package com.android.fxcontacts;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.android.fxcontacts.TextHighlightingAnimation.TextWithHighlighting;
import com.android.fxcontacts.model.ContactsSource;
import com.android.fxcontacts.model.Sources;
import com.android.fxcontacts.ui.ContactsPreferences;
import com.android.fxcontacts.ui.ContactsPreferencesActivity;
import com.android.fxcontacts.ui.ContactsPreferencesActivity.Prefs;
import com.android.fxcontacts.util.AccountSelectionUtil;
import com.android.fxcontacts.util.Constants;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.Uri.Builder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Intents.Insert;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.QuickContactBadge;
import android.widget.SectionIndexer;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AbsListView.OnScrollListener;

public class ContactsActivity extends ListActivity implements
		View.OnCreateContextMenuListener, View.OnClickListener,
		View.OnKeyListener, TextWatcher, TextView.OnEditorActionListener,
		OnFocusChangeListener, OnTouchListener
{

	private static final String TAG = "ContactsActivity";

	private static final boolean ENABLE_ACTION_ICON_OVERLAYS = true;

	private static final String LIST_STATE_KEY = "liststate";
	private static final String SHORTCUT_ACTION_KEY = "shortcutAction";

	// ContactsConstract.Intents.UI constant string
	public static final String FILTER_CONTACTS_ACTION = "com.android.contacts.action.FILTER_CONTACTS";
	public static final String FILTER_TEXT_EXTRA_KEY = "com.android.contacts.extra.FILTER_TEXT";
	public static final String LIST_DEFAULT = "com.android.contacts.action.LIST_DEFAULT";
	public static final String LIST_GROUP_ACTION = "com.android.contacts.action.LIST_GROUP";
	public static final String LIST_ALL_CONTACTS_ACTION = "com.android.contacts.action.LIST_ALL_CONTACTS";
	public static final String LIST_STARRED_ACTION = "com.android.contacts.action.LIST_STARRED";
	public static final String LIST_FREQUENT_ACTION = "com.android.contacts.action.LIST_FREQUENT";
	public static final String LIST_STREQUENT_ACTION = "com.android.contacts.action.LIST_STREQUENT";
	public static final String LIST_CONTACTS_WITH_PHONES_ACTION = "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES";
	public static final String GROUP_NAME_EXTRA_KEY = "com.android.contacts.extra.GROUP";

	// Contacts constant string
	public static final String SORT_KEY_PRIMARY = "sort_key";
	public static final String SORT_KEY_ALTERNATIVE = "sort_key_alt";
	public static final String REQUESTING_PACKAGE_PARAM_KEY = "requesting_package";
	public static final Uri CONTENT_MULTI_VCARD_URI = Uri.withAppendedPath(Contacts.CONTENT_URI,
    "as_multi_vcard");

	// ContactsCounts constant
	/**
	 * The array of address book index titles, which are returned in the same
	 * order as the data in the cursor
	 */
	public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles";
	/**
	 * The array of group counts for the corresponding group. Contains the same
	 * number of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array
	 */
	public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts";

	// ContactsContract.QuickContact constant
	public static final String ACTION_QUICK_CONTACT = "com.android.contacts.action.QUICK_CONTACT";
	public static final String EXTRA_MODE = "mode";
	public static final String EXTRA_EXCLUDE_MIMES = "exclude_mimes";

	// menu items
	static final int MENU_ITEM_VIEW_CONTACT = 1;
	static final int MENU_ITEM_CALL = 2;
	static final int MENU_ITEM_EDIT_BEFORE_CALL = 3;
	static final int MENU_ITEM_SEND_SMS = 4;
	static final int MENU_ITEM_SEND_IM = 5;
	static final int MENU_ITEM_EDIT = 6;
	static final int MENU_ITEM_DELETE = 7;
	static final int MENU_ITEM_TOGGLE_STAR = 8;

	// sub-activity index
	private static final int SUBACTIVITY_NEW_CONTACT = 1;
	private static final int SUBACTIVITY_VIEW_CONTACT = 2;
	private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
	private static final int SUBACTIVITY_SEARCH = 4;
	private static final int SUBACTIVITY_FILTER = 5;

	private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;

	/**
	 * The action for the join contact activity.
	 * <p>
	 * Input: extra field {@link #EXTRA_AGGREGATE_ID} is the aggregate ID.
	 * 
	 * TODO: move to {@link ContactsContract}.
	 */
	public static final String JOIN_AGGREGATE = "com.android.contacts.action.JOIN_AGGREGATE";

	public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";

	/**
	 * Used with {@link #JOIN_AGGREGATE} to give it the target for aggregation.
	 * <p>
	 * Type: LONG
	 */
	public static final String EXTRA_AGGREGATE_ID = "com.android.contacts.action.AGGREGATE_ID";
	
    /**
     * Used with {@link #JOIN_AGGREGATE} to give it the name of the aggregation target.
     * <p>
     * Type: STRING
     */
    @Deprecated
    public static final String EXTRA_AGGREGATE_NAME =
            "com.android.contacts.action.AGGREGATE_NAME";

	public static final String AUTHORITIES_FILTER_KEY = "authorities";

	/**
	 * Add this query parameter to a URI to get back row counts grouped by the
	 * address book
	 * index as cursor extras. For most languages it is the first letter of the
	 * sort key.
	 * This parameter does not affect the main content of the cursor.
	 */
	public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras";
	private static final Uri CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS = buildSectionIndexerUri(Contacts.CONTENT_URI);

	/** Mask for picker mode */
	static final int MODE_MASK_PICKER = 0x80000000;
	/** Mask for no presence mode */
	static final int MODE_MASK_NO_PRESENCE = 0x40000000;
	/** Mask for enabling list filtering */
	static final int MODE_MASK_NO_FILTER = 0x20000000;
	/** Mask for having a "create new contact" header in the list */
	static final int MODE_MASK_CREATE_NEW = 0x10000000;
	/** Mask for showing photos in the list */
	static final int MODE_MASK_SHOW_PHOTOS = 0x08000000;
	/**
	 * Mask for hiding additional information e.g. primary phone number in the
	 * list
	 */
	static final int MODE_MASK_NO_DATA = 0x04000000;
	/** Mask for showing a call button in the list */
	static final int MODE_MASK_SHOW_CALL_BUTTON = 0x02000000;
	/** Mask to disable quickcontact (images will show as normal images) */
	static final int MODE_MASK_DISABLE_QUIKCCONTACT = 0x01000000;
	/** Mask to show the total number of contacts at the top */
	static final int MODE_MASK_SHOW_NUMBER_OF_CONTACTS = 0x00800000;

	/** Unknown mode */
	static final int MODE_UNKNOWN = 0;
	/** Default mode */
	static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
	/** Custom mode */
	static final int MODE_CUSTOM = 8;
	/** Show all starred contacts */
	static final int MODE_STARRED = 20 | MODE_MASK_SHOW_PHOTOS;
	/** Show frequently contacted contacts */
	static final int MODE_FREQUENT = 30 | MODE_MASK_SHOW_PHOTOS;
	/** Show starred and the frequent */
	static final int MODE_STREQUENT = 35 | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_SHOW_CALL_BUTTON;
	/** Show all contacts and pick them when clicking */
	static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER
			| MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
	/** Show all contacts as well as the option to create a new one */
	static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER
			| MODE_MASK_CREATE_NEW | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_DISABLE_QUIKCCONTACT;
	/** Show all people through the legacy provider and pick them when clicking */
	static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER
			| MODE_MASK_DISABLE_QUIKCCONTACT;
	/**
	 * Show all people through the legacy provider as well as the option to
	 * create a new one
	 */
	static final int MODE_LEGACY_PICK_OR_CREATE_PERSON = 44 | MODE_MASK_PICKER
			| MODE_MASK_CREATE_NEW | MODE_MASK_DISABLE_QUIKCCONTACT;
	/**
	 * Show all contacts and pick them when clicking, and allow creating a new
	 * contact
	 */
	static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER
			| MODE_MASK_CREATE_NEW | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_DISABLE_QUIKCCONTACT;
	/** Show all phone numbers and pick them when clicking */
	static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER
			| MODE_MASK_NO_PRESENCE;
	/**
	 * Show all phone numbers through the legacy provider and pick them when
	 * clicking
	 */
	static final int MODE_LEGACY_PICK_PHONE = 51 | MODE_MASK_PICKER
			| MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
	/** Show all postal addresses and pick them when clicking */
	static final int MODE_PICK_POSTAL = 55 | MODE_MASK_PICKER
			| MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
	/** Show all postal addresses and pick them when clicking */
	static final int MODE_LEGACY_PICK_POSTAL = 56 | MODE_MASK_PICKER
			| MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
	static final int MODE_GROUP = 57 | MODE_MASK_SHOW_PHOTOS;
	/** Run a search query */
	static final int MODE_QUERY = 60 | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_NO_FILTER | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
	/** Run a search query in PICK mode, but that still launches to VIEW */
	static final int MODE_QUERY_PICK_TO_VIEW = 65 | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_PICKER | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;

	/** Show join suggestions followed by an A-Z list */
	static final int MODE_JOIN_CONTACT = 70 | MODE_MASK_PICKER
			| MODE_MASK_NO_PRESENCE | MODE_MASK_NO_DATA | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_DISABLE_QUIKCCONTACT;

	/** Run a search query in a PICK mode */
	static final int MODE_QUERY_PICK = 75 | MODE_MASK_SHOW_PHOTOS
			| MODE_MASK_NO_FILTER | MODE_MASK_PICKER
			| MODE_MASK_DISABLE_QUIKCCONTACT
			| MODE_MASK_SHOW_NUMBER_OF_CONTACTS;

	/** Run a search query in a PICK_PHONE mode */
	static final int MODE_QUERY_PICK_PHONE = 80 | MODE_MASK_NO_FILTER
			| MODE_MASK_PICKER | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;

	/** Run a search query in PICK mode, but that still launches to EDIT */
	static final int MODE_QUERY_PICK_TO_EDIT = 85 | MODE_MASK_NO_FILTER
			| MODE_MASK_SHOW_PHOTOS | MODE_MASK_PICKER
			| MODE_MASK_SHOW_NUMBER_OF_CONTACTS;

	/**
	 * An action used to do perform search while in a contact picker. It is
	 * initiated by the ContactActivity itself.
	 */
	private static final String ACTION_SEARCH_INTERNAL = "com.android.contacts.INTERNAL_SEARCH";

	/** Maximum number of suggestions shown for joining aggregates */
	static final int MAX_SUGGESTIONS = 4;

	static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
			Contacts._ID, // 0
			"display_name", // 1 Contacts.DISPLAY_NAME_PRIMARY,
			"display_name_alt", // 2 Contacts.DISPLAY_NAME_ALTERNATIVE
			SORT_KEY_PRIMARY, // 3 Contacts.SORT_KEY_PRIMARY,
			Contacts.STARRED, // 4
			Contacts.TIMES_CONTACTED, // 5
			Contacts.CONTACT_PRESENCE, // 6
			Contacts.PHOTO_ID, // 7
			Contacts.LOOKUP_KEY, // 8
			"phonetic_name", // 9 Contacts.PHONETIC_NAME,
			Contacts.HAS_PHONE_NUMBER // 10
	};

	static final String[] CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL = new String[] {
			Contacts._ID, // 0
			"display_name", // 1 Contacts.DISPLAY_NAME_PRIMARY,
			"display_name_alt", // 2 Contacts.DISPLAY_NAME_ALTERNATIVE
			SORT_KEY_PRIMARY, // 3 Contacts.SORT_KEY_PRIMARY,
			Contacts.STARRED, // 4
			Contacts.TIMES_CONTACTED, // 5
			Contacts.CONTACT_PRESENCE, // 6
			Contacts.PHOTO_ID, // 7
			Contacts.LOOKUP_KEY, // 8
			"phonetic_name", // 9 Contacts.PHONETIC_NAME,
	// email lookup doesn't included HAS_PHONE_NUMBER in projection
	};

	static final String[] CONTACTS_SUMMARY_FILTER_PROJECTION = new String[] {
			Contacts._ID, // 0
			"display_name", // 1 Contacts.DISPLAY_NAME_PRIMARY,
			"display_name_alt", // 2 Contacts.DISPLAY_NAME_ALTERNATIVE
			SORT_KEY_PRIMARY, // 3 Contacts.SORT_KEY_PRIMARY,
			Contacts.STARRED, // 4
			Contacts.TIMES_CONTACTED, // 5
			Contacts.CONTACT_PRESENCE, // 6
			Contacts.PHOTO_ID, // 7
			Contacts.LOOKUP_KEY, // 8
			"phonetic_name", // 9 Contacts.PHONETIC_NAME,
			Contacts.HAS_PHONE_NUMBER, // 10
			Data.MIMETYPE, // 11
			Data.DATA1, // 12
			Data.DATA4 // 13
	};

	static final String[] LEGACY_PEOPLE_PROJECTION = new String[] {
			Contacts._ID, // 0
			Contacts.DISPLAY_NAME, // 1
			Contacts.DISPLAY_NAME, // 2
			Contacts.DISPLAY_NAME, // 3
			Contacts.STARRED, // 4
			Contacts.TIMES_CONTACTED, // 5
			StatusUpdates.PRESENCE_STATUS, // 6
	};

	static final int SUMMARY_ID_COLUMN_INDEX = 0;
	static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
	static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
	static final int SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
	static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
	static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 5;
	static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
	static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
	static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
	static final int SUMMARY_PHONETIC_NAME_COLUMN_INDEX = 9;
	static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 10;
	static final int SUMMARY_DATA_MIMETYPE_COLUMN_INDEX = 11;
	static final int SUMMARY_DATA_DATA1_COLUMN_INDEX = 12;
	static final int SUMMARY_DATA_DATA4_COLUMN_INDEX = 13;

	static final String[] PHONES_PROJECTION = new String[] { Phone._ID, // 0
			Phone.TYPE, // 1
			Phone.LABEL, // 2
			Phone.NUMBER, // 3
			Phone.DISPLAY_NAME, // 4
			Phone.CONTACT_ID, // 5
	};

	static final String[] LEGACY_PHONES_PROJECTION = new String[] { Phones._ID, // 0
			Phones.TYPE, // 1
			Phones.LABEL, // 2
			Phones.NUMBER, // 3
			People.DISPLAY_NAME, // 4
	};

	static final int PHONE_ID_COLUMN_INDEX = 0;
	static final int PHONE_TYPE_COLUMN_INDEX = 1;
	static final int PHONE_LABEL_COLUMN_INDEX = 2;
	static final int PHONE_NUMBER_COLUMN_INDEX = 3;
	static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 4;
	static final int PHONE_CONTACT_ID_COLUMN_INDEX = 5;

	static final String[] POSTALS_PROJECTION = new String[] {
			StructuredPostal._ID, // 0
			StructuredPostal.TYPE, // 1
			StructuredPostal.LABEL, // 2
			StructuredPostal.DATA, // 3
			StructuredPostal.DISPLAY_NAME, // 4
	};
	static final String[] LEGACY_POSTALS_PROJECTION = new String[] {
			ContactMethods._ID, // 0
			ContactMethods.TYPE, // 1
			ContactMethods.LABEL, // 2
			ContactMethods.DATA, // 3
			People.DISPLAY_NAME, // 4
	};

	static final String[] RAW_CONTACTS_PROJECTION = new String[] {
			RawContacts._ID, // 0
			RawContacts.CONTACT_ID, // 1
			RawContacts.ACCOUNT_TYPE, // 2
	};

	static final int POSTAL_ID_COLUMN_INDEX = 0;
	static final int POSTAL_TYPE_COLUMN_INDEX = 1;
	static final int POSTAL_LABEL_COLUMN_INDEX = 2;
	static final int POSTAL_ADDRESS_COLUMN_INDEX = 3;
	static final int POSTAL_DISPLAY_NAME_COLUMN_INDEX = 4;

	private static final int QUERY_TOKEN = 42;

	static final String KEY_PICKER_MODE = "picker_mode";

	private ContactItemListAdapter mAdapter;

	int mMode = MODE_DEFAULT;

	private QueryHandler mQueryHandler;
	private boolean mJustCreated;
	private boolean mSyncEnabled;
	Uri mSelectedContactUri;

	private boolean mDisplayOnlyPhones;

	private Uri mGroupUri;

	private long mQueryAggregateId;

	private ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
	private int mWritableSourcesCnt;
	private int mReadOnlySourcesCnt;

	/** Used to keep track of the scroll state of the list. */
	private Parcelable mListState = null;

	private String mShortcutAction;

	/** Internal query type when in mode {@link #MODE_QUERY_PICK_TO_VIEW}. */
	private int mQueryMode = QUERY_MODE_NONE;

	private static final int QUERY_MODE_NONE = -1;
	private static final int QUERY_MODE_MAILTO = 1;
	private static final int QUERY_MODE_TEL = 2;

	private int mProviderStatus = ProviderStatus.STATUS_NORMAL;

	private boolean mSearchMode;
	private boolean mSearchResultsMode;
	private boolean mShowNumberOfContacts;

	private boolean mShowSearchSnippets;
	private boolean mSearchInitiated;

	private String mInitialFilter;

	private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP
			+ "=1";
	private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER
			+ "=1";

	/**
	 * In the {@link #MODE_JOIN_CONTACT} determines whether we display a list
	 * item with the label
	 * "Show all contacts" or actually show all contacts
	 */
	private boolean mJoinModeShowAllContacts;

	/** The ID of the special item described above. */
	private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;

	// Uri matcher for contact id
	private static final int CONTACTS_ID = 1001;
	private static final UriMatcher sContactsIdMatcher;

	private ContactPhotoLoader mPhotoLoader;

	final String[] sLookupProjection = new String[] { Contacts.LOOKUP_KEY };

	static
	{
		sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#",
				CONTACTS_ID);
	}

	private class DeleteClickListener implements
			DialogInterface.OnClickListener
	{
		public void onClick(DialogInterface dialog, int which)
		{
			if (mSelectedContactUri != null)
			{
				getContentResolver().delete(mSelectedContactUri, null, null);
			}
		}
	}

	/**
	 * A {@link TextHighlightingAnimation} that redraws just the contact display
	 * name in a
	 * list item.
	 */
	private static class NameHighlightingAnimation extends
			TextHighlightingAnimation
	{
		private final ListView mListView;

		private NameHighlightingAnimation(ListView listView, int duration)
		{
			super(duration);
			this.mListView = listView;
		}

		/**
		 * Redraws all visible items of the list corresponding to contacts
		 */
		@Override
		protected void invalidate()
		{
			int childCount = mListView.getChildCount();
			for (int i = 0; i < childCount; i++)
			{
				View itemView = mListView.getChildAt(i);
				if (itemView instanceof ContactListItemView)
				{
					final ContactListItemView view = (ContactListItemView) itemView;
					view.getNameTextView().invalidate();
				}
			}
		}

		@Override
		protected void onAnimationStarted()
		{
			mListView.setScrollingCacheEnabled(false);
		}

		@Override
		protected void onAnimationEnded()
		{
			mListView.setScrollingCacheEnabled(true);
		}
	}

	// The size of a home screen shortcut icon.
	private int mIconSize;
	private ContactsPreferences mContactsPrefs;
	private int mDisplayOrder;
	private int mSortOrder;
	private boolean mHighlightWhenScrolling;
	private TextHighlightingAnimation mHighlightingAnimation;
	private SearchEditText mSearchEditText;

	/**
	 * An approximation of the background color of the pinned header. This color
	 * is used when the pinned header is being pushed up. At that point the
	 * header
	 * "fades away". Rather than computing a faded bitmap based on the 9-patch
	 * normally used for the background, we will use a solid color, which will
	 * provide better performance and reduced complexity.
	 */
	private int mPinnedHeaderBackgroundColor;

	private ContentObserver mProviderStatusObserver = new ContentObserver(
			new Handler())
	{

		@Override
		public void onChange(boolean selfChange)
		{
			checkProviderState(true);
		}
	};

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle icicle)
	{
		super.onCreate(icicle);

		mIconSize = getResources().getDimensionPixelSize(
				android.R.dimen.app_icon_size);
		mContactsPrefs = new ContactsPreferences(this);
		mPhotoLoader = new ContactPhotoLoader(this,
				R.drawable.ic_contact_list_picture);

		// Resolve the intent
		final Intent intent = getIntent();

		// Allow the title to be set to a custom String using an extra on the
		// intent
		String title = intent
				.getStringExtra("com.android.contacts.extra.TITLE_EXTRA");// intent.UI.TITLE_EXTRA
		if (title != null)
		{
			setTitle(title);
		}

		String action = intent.getAction();
		String component = intent.getComponent().getClassName();
		if (FILTER_CONTACTS_ACTION.equals(action))
		{
			mSearchMode = true;
			mShowSearchSnippets = true;
			Bundle extras = intent.getExtras();
			if (extras != null)
			{
				mInitialFilter = extras.getString(FILTER_TEXT_EXTRA_KEY);
				String originalAction = extras
						.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
				if (originalAction != null)
				{
					action = originalAction;
				}
				String originalComponent = extras
						.getString(ContactsSearchManager.ORIGINAL_COMPONENT_EXTRA_KEY);
				if (originalComponent != null)
				{
					component = originalComponent;
				}
			} else
			{
				mInitialFilter = null;
			}
		}

		Log.i(TAG, "Called with action: " + action);
		mMode = MODE_UNKNOWN;
		if (LIST_DEFAULT.equals(action)
				|| FILTER_CONTACTS_ACTION.equals(action))
		{
			mMode = MODE_DEFAULT;
			// When mDefaultMode is true the mode is set in onResume(), since
			// the preferneces
			// activity may change it whenever this activity isn't running
		} else if (LIST_GROUP_ACTION.equals(action))
		{
			mMode = MODE_GROUP;
			String groupName = intent.getStringExtra(GROUP_NAME_EXTRA_KEY);
			if (TextUtils.isEmpty(groupName))
			{
				finish();
				return;
			}
			buildUserGroupUri(groupName);
		} else if (LIST_ALL_CONTACTS_ACTION.equals(action))
		{
			mMode = MODE_CUSTOM;
			mDisplayOnlyPhones = false;
		} else if (LIST_STARRED_ACTION.equals(action))
		{
			mMode = mSearchMode ? MODE_DEFAULT : MODE_STARRED;
		} else if (LIST_FREQUENT_ACTION.equals(action))
		{
			mMode = mSearchMode ? MODE_DEFAULT : MODE_FREQUENT;
		} else if (LIST_STREQUENT_ACTION.equals(action))
		{
			mMode = mSearchMode ? MODE_DEFAULT : MODE_STREQUENT;
		} else if (LIST_CONTACTS_WITH_PHONES_ACTION.equals(action))
		{
			mMode = MODE_CUSTOM;
			mDisplayOnlyPhones = true;
		} else if (Intent.ACTION_PICK.equals(action))
		{
			// XXX These should be showing the data from the URI given in
			// the Intent.
			final String type = intent.resolveType(this);
			if (Contacts.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_PICK_CONTACT;
			} else if (People.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_PERSON;
			} else if (Phone.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_PICK_PHONE;
			} else if (Phones.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_PHONE;
			} else if (StructuredPostal.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_PICK_POSTAL;
			} else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_POSTAL;
			}
		} else if (Intent.ACTION_PICK.equals(action))
		{
			// XXX These should be showing the data from the URI given in
			// the Intent.
			final String type = intent.resolveType(this);
			if (Contacts.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_PICK_CONTACT;
			} else if (People.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_PERSON;
			} else if (Phone.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_PICK_PHONE;
			} else if (Phones.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_PHONE;
			} else if (StructuredPostal.CONTENT_TYPE.equals(type))
			{
				mMode = MODE_PICK_POSTAL;
			} else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_POSTAL;
			}
		} else if (Intent.ACTION_GET_CONTENT.equals(action))
		{
			final String type = intent.resolveType(this);
			if (Contacts.CONTENT_ITEM_TYPE.equals(type))
			{
				if (mSearchMode)
				{
					mMode = MODE_PICK_CONTACT;
				} else
				{
					mMode = MODE_PICK_OR_CREATE_CONTACT;
				}
			} else if (Phone.CONTENT_ITEM_TYPE.equals(type))
			{
				mMode = MODE_PICK_PHONE;
			} else if (Phones.CONTENT_ITEM_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_PHONE;
			} else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(type))
			{
				mMode = MODE_PICK_POSTAL;
			} else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type))
			{
				mMode = MODE_LEGACY_PICK_POSTAL;
			} else if (People.CONTENT_ITEM_TYPE.equals(type))
			{
				if (mSearchMode)
				{
					mMode = MODE_LEGACY_PICK_PERSON;
				} else
				{
					mMode = MODE_LEGACY_PICK_OR_CREATE_PERSON;
				}
			}

		} else if (Intent.ACTION_INSERT_OR_EDIT.equals(action))
		{
			mMode = MODE_INSERT_OR_EDIT_CONTACT;
		} else if (Intent.ACTION_SEARCH.equals(action))
		{
			// See if the suggestion was clicked with a search action key (call
			// button)
			if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG)))
			{
				String query = intent.getStringExtra(SearchManager.QUERY);
				if (!TextUtils.isEmpty(query))
				{
					Intent newIntent = new Intent(ACTION_CALL_PRIVILEGED, Uri
							.fromParts("tel", query, null));
					startActivity(newIntent);
				}
				finish();
				return;
			}
			// See if search request has extras to specify query
			if (intent.hasExtra(Insert.EMAIL))
			{
				mMode = MODE_QUERY_PICK_TO_VIEW;
				mQueryMode = QUERY_MODE_MAILTO;
				mInitialFilter = intent.getStringExtra(Insert.EMAIL);
			} else if (intent.hasExtra(Insert.PHONE))
			{
				mMode = MODE_QUERY_PICK_TO_VIEW;
				mQueryMode = QUERY_MODE_TEL;
				mInitialFilter = intent.getStringExtra(Insert.PHONE);
			} else
			{
				// Otherwise handle the more normal search case
				mMode = MODE_QUERY;
				mShowSearchSnippets = true;
				mInitialFilter = getIntent()
						.getStringExtra(SearchManager.QUERY);
			}
			mSearchResultsMode = true;
		} else if (ACTION_SEARCH_INTERNAL.equals(action))
		{
			String originalAction = null;
			Bundle extras = intent.getExtras();
			if (extras != null)
			{
				originalAction = extras
						.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
			}
			mShortcutAction = intent.getStringExtra(SHORTCUT_ACTION_KEY);

			if (Intent.ACTION_INSERT_OR_EDIT.equals(originalAction))
			{
				mMode = MODE_QUERY_PICK_TO_EDIT;
				mShowSearchSnippets = true;
				mInitialFilter = getIntent()
						.getStringExtra(SearchManager.QUERY);
			} else if (mShortcutAction != null && intent.hasExtra(Insert.PHONE))
			{
				mMode = MODE_QUERY_PICK_PHONE;
				mQueryMode = QUERY_MODE_TEL;
				mInitialFilter = intent.getStringExtra(Insert.PHONE);
			} else
			{
				mMode = MODE_QUERY_PICK;
				mQueryMode = QUERY_MODE_NONE;
				mShowSearchSnippets = true;
				mInitialFilter = getIntent()
						.getStringExtra(SearchManager.QUERY);
			}
			mSearchResultsMode = true;
			// Since this is the filter activity it receives all intents
			// dispatched from the SearchManager for security reasons
			// so we need to re-dispatch from here to the intended target.
		} else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action))
		{
			Uri data = intent.getData();
			Uri telUri = null;
			if (sContactsIdMatcher.match(data) == CONTACTS_ID)
			{
				long contactId = Long.valueOf(data.getLastPathSegment());
				final Cursor cursor = queryPhoneNumbers(contactId);
				if (cursor != null)
				{
					if (cursor.getCount() == 1 && cursor.moveToFirst())
					{
						int phoneNumberIndex = cursor
								.getColumnIndex(Phone.NUMBER);
						String phoneNumber = cursor.getString(phoneNumberIndex);
						telUri = Uri.parse("tel:" + phoneNumber);
					}
					cursor.close();
				}
			}
			// See if the suggestion was clicked with a search action key (call
			// button)
			Intent newIntent;
			if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))
					&& telUri != null)
			{
				newIntent = new Intent(ACTION_CALL_PRIVILEGED, telUri);
			} else
			{
				newIntent = new Intent(Intent.ACTION_VIEW, data);
			}
			startActivity(newIntent);
			finish();
			return;
		} else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action))
		{
			Intent newIntent = new Intent(ACTION_CALL_PRIVILEGED, intent
					.getData());
			startActivity(newIntent);
			finish();
			return;
		} else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED
				.equals(action))
		{
			// TODO actually support this in EditContactActivity.
			String number = intent.getData().getSchemeSpecificPart();
			Intent newIntent = new Intent(Intent.ACTION_INSERT,
					Contacts.CONTENT_URI);
			newIntent.putExtra(Intents.Insert.PHONE, number);
			startActivity(newIntent);
			finish();
			return;
		}

		if (JOIN_AGGREGATE.equals(action))
		{
			if (mSearchMode)
			{
				mMode = MODE_PICK_CONTACT;
			} else
			{
				mMode = MODE_JOIN_CONTACT;
				mQueryAggregateId = intent.getLongExtra(EXTRA_AGGREGATE_ID, -1);
				if (mQueryAggregateId == -1)
				{
					Log.e(TAG, "Intent " + action
							+ " is missing required extra: "
							+ EXTRA_AGGREGATE_ID);
					setResult(RESULT_CANCELED);
					finish();
				}
			}
		}

		if (mMode == MODE_UNKNOWN)
		{
			mMode = MODE_DEFAULT;
		}

		if (((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 || mSearchMode)
				&& !mSearchResultsMode)
		{
			mShowNumberOfContacts = true;
		}

		if (mMode == MODE_JOIN_CONTACT)
		{
			setContentView(R.layout.contacts_list_content_join);
			TextView blurbView = (TextView) findViewById(R.id.join_contact_blurb);

			String blurb = getString(R.string.blurbJoinContactDataWith,
					getContactDisplayName(mQueryAggregateId));
			blurbView.setText(blurb);
			mJoinModeShowAllContacts = true;
		} else if (mSearchMode)
		{
			setContentView(R.layout.contacts_search_content);
		} else if (mSearchResultsMode)
		{
			setContentView(R.layout.contacts_list_search_results);
			TextView titleText = (TextView) findViewById(R.id.search_results_for);
			titleText.setText(Html.fromHtml(getString(
					R.string.search_results_for, "<b>" + mInitialFilter
							+ "</b>")));
		} else
		{
			setContentView(R.layout.contacts_list_content);
		}

		setupListView();
		if (mSearchMode)
		{
			setupSearchView();
		}

		mQueryHandler = new QueryHandler(this);
		mJustCreated = true;

		mSyncEnabled = true;
	}

	/**
	 * Register an observer for provider status changes - we will need to
	 * reflect them in the UI.
	 */
	private void registerProviderStatusObserver()
	{
		getContentResolver().registerContentObserver(
				ProviderStatus.CONTENT_URI, false, mProviderStatusObserver);
	}

	/**
	 * Register an observer for provider status changes - we will need to
	 * reflect them in the UI.
	 */
	private void unregisterProviderStatusObserver()
	{
		getContentResolver().unregisterContentObserver(mProviderStatusObserver);
	}

	private void setupListView()
	{
		final ListView list = getListView();
		final LayoutInflater inflater = getLayoutInflater();

		mHighlightingAnimation = new NameHighlightingAnimation(list,
				TEXT_HIGHLIGHTING_ANIMATION_DURATION);

		// Tell list view to not show dividers. We'll do it ourself so that we
		// can *not* show
		// them when an A-Z headers is visible.
		list.setDividerHeight(0);
		list.setOnCreateContextMenuListener(this);

		mAdapter = new ContactItemListAdapter(this);
		setListAdapter(mAdapter);

		if (list instanceof PinnedHeaderListView
				&& mAdapter.getDisplaySectionHeadersEnabled())
		{
			mPinnedHeaderBackgroundColor = getResources().getColor(
					R.color.pinned_header_background);
			PinnedHeaderListView pinnedHeaderList = (PinnedHeaderListView) list;
			View pinnedHeader = inflater.inflate(R.layout.list_section, list,
					false);
			pinnedHeaderList.setPinnedHeaderView(pinnedHeader);
		}

		list.setOnScrollListener(mAdapter);
		list.setOnKeyListener(this);
		list.setOnFocusChangeListener(this);
		list.setOnTouchListener(this);

		// We manually save/restore the listview state
		list.setSaveEnabled(false);
	}

	/**
	 * Configures search UI.
	 */
	private void setupSearchView()
	{
		mSearchEditText = (SearchEditText) findViewById(R.id.search_src_text);
		mSearchEditText.addTextChangedListener(this);
		mSearchEditText.setOnEditorActionListener(this);
		mSearchEditText.setText(mInitialFilter);
	}

	private String getContactDisplayName(long contactId)
	{
		String contactName = null;
		Cursor c = getContentResolver().query(
				ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
				new String[] { Contacts.DISPLAY_NAME }, null, null, null);
		try
		{
			if (c != null && c.moveToFirst())
			{
				contactName = c.getString(0);
			}
		} finally
		{
			if (c != null)
			{
				c.close();
			}
		}

		if (contactName == null)
		{
			contactName = "";
		}

		return contactName;
	}

	private int getSummaryDisplayNameColumnIndex()
	{
		if (mDisplayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY)
		{
			return SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
		} else
		{
			return SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
		}
	}

	@Override
	public void onClick(View v)
	{
		int id = v.getId();
		switch (id)
		{
		// TODO a better way of identifying the button
		case android.R.id.button1:
		{
			final int position = (Integer) v.getTag();
			Cursor c = mAdapter.getCursor();
			if (c != null)
			{
				c.moveToPosition(position);
				callContact(c);
			}
			break;
		}
		}
	}

	private void setEmptyText()
	{
		if (mMode == MODE_JOIN_CONTACT || mSearchMode)
		{
			return;
		}

		TextView empty = (TextView) findViewById(R.id.emptyText);
		if (mDisplayOnlyPhones)
		{
			empty.setText(getText(R.string.noContactsWithPhoneNumbers));
		} else if (mMode == MODE_STREQUENT || mMode == MODE_STARRED)
		{
			empty.setText(getText(R.string.noFavoritesHelpText));
		} else if (mMode == MODE_QUERY || mMode == MODE_QUERY_PICK
				|| mMode == MODE_QUERY_PICK_PHONE
				|| mMode == MODE_QUERY_PICK_TO_VIEW
				|| mMode == MODE_QUERY_PICK_TO_EDIT)
		{
			empty.setText(getText(R.string.noMatchingContacts));
		} else
		{
			boolean hasSim = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
					.hasIccCard();
			boolean createShortcut = Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction());
			if (isSyncActive())
			{
				if (createShortcut)
				{
					// Help text is the same no matter whether there is SIM or not.
					empty.setText(getText(R.string.noContactsHelpTextWithSyncForCreateShortcut));
				} else if (hasSim)
				{
					empty.setText(getText(R.string.noContactsHelpTextWithSync));
				} else
				{
					empty.setText(getText(R.string.noContactsNoSimHelpTextWithSync));
				}
			} else
			{
				if (createShortcut)
				{
					// Help text is the same no matter whether there is SIM or not.
					empty.setText(getText(R.string.noContactsHelpTextForCreateShortcut));
				} else if (hasSim)
				{
					empty.setText(getText(R.string.noContactsHelpText));
				} else
				{
					empty.setText(getText(R.string.noContactsNoSimHelpText));
				}
			}
		}
	}

	/**
	 * Event handler for the use case where the user starts typing without
	 * bringing up the search UI first.
	 * 
	 * @Override
	 */
	public boolean onKey(View v, int keyCode, KeyEvent event)
	{
		if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0
				&& !mSearchInitiated)
		{
			int unicodeChar = event.getUnicodeChar();
			if (unicodeChar != 0)
			{
				mSearchInitiated = true;
				startSearch(new String(new int[] { unicodeChar }, 0, 1), false,
						null, false);
				return true;
			}
		}
		return false;
	}

	/**
	 * Event handler for search UI.
	 * 
	 * @Override
	 */
	public void afterTextChanged(Editable s)
	{
		onSearchTextChanged();

	}

	@Override
	public void beforeTextChanged(CharSequence s, int start, int count,
			int after)
	{
		// TODO Auto-generated method stub

	}

	@Override
	public void onTextChanged(CharSequence s, int start, int before, int count)
	{
		// TODO Auto-generated method stub

	}

	/**
	 * Event handler for search UI.
	 * 
	 * @Override
	 */
	public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
	{
		if (actionId == EditorInfo.IME_ACTION_DONE)
		{
			hideSoftKeyboard();
			if (TextUtils.isEmpty(getTextFilter()))
			{
				finish();
			}
			return true;
		}
		return false;
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event)
	{
		switch (keyCode)
		{
		case KeyEvent.KEYCODE_CALL:
		{
			if (callSelection())
			{
				return true;
			}
			break;
		}

		case KeyEvent.KEYCODE_DEL:
		{
			if (deleteSelection())
			{
				return true;
			}
			break;
		}
		}

		return super.onKeyDown(keyCode, event);
	}

	private boolean deleteSelection()
	{
		if ((mMode & MODE_MASK_PICKER) != 0)
		{
			return false;
		}

		final int position = getListView().getSelectedItemPosition();
		if (position != ListView.INVALID_POSITION)
		{
			Uri contactUri = getContactUri(position);
			if (contactUri != null)
			{
				doContactDelete(contactUri);
				return true;
			}
		}
		return false;
	}

	/**
	 * Prompt the user before deleting the given {@link Contacts} entry.
	 */
	protected void doContactDelete(Uri contactUri)
	{
		mReadOnlySourcesCnt = 0;
		mWritableSourcesCnt = 0;
		mWritableRawContactIds.clear();

		Sources sources = Sources.getInstance(ContactsActivity.this);
		Cursor c = getContentResolver().query(RawContacts.CONTENT_URI,
				RAW_CONTACTS_PROJECTION,
				RawContacts.CONTACT_ID + "=" + ContentUris.parseId(contactUri),
				null, null);
		if (c != null)
		{
			try
			{
				while (c.moveToNext())
				{
					final String accountType = c.getString(2);
					final long rawContactId = c.getLong(0);
					ContactsSource contactsSource = sources.getInflatedSource(
							accountType, ContactsSource.LEVEL_SUMMARY);
					if (contactsSource != null && contactsSource.readOnly)
					{
						mReadOnlySourcesCnt += 1;
					} else
					{
						mWritableSourcesCnt += 1;
						mWritableRawContactIds.add(rawContactId);
					}
				}
			} finally
			{
				c.close();
			}
		}

		mSelectedContactUri = contactUri;
		if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt > 0)
		{
			showDialog(R.id.dialog_readonly_contact_delete_confirmation);
		} else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0)
		{
			showDialog(R.id.dialog_readonly_contact_hide_confirmation);
		} else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1)
		{
			showDialog(R.id.dialog_multiple_contact_delete_confirmation);
		} else
		{
			showDialog(R.id.dialog_delete_contact_confirmation);
		}
	}

	/**
	 * Dismisses the soft keyboard when the list takes focus.
	 * 
	 * @Override
	 */
	public void onFocusChange(View view, boolean hasFocus)
	{
		if (view == getListView() && hasFocus)
		{
			hideSoftKeyboard();
		}
	}

	/**
	 * Dismisses the soft keyboard when the list takes focus.
	 * 
	 * @Override
	 */
	public boolean onTouch(View view, MotionEvent event)
	{
		if (view == getListView())
		{
			hideSoftKeyboard();
		}
		return false;
	}

	/**
	 * Dismisses the search UI along with the keyboard if the filter text is
	 * empty.
	 */
	public boolean onKeyPreIme(int keyCode, KeyEvent event)
	{
		if (mSearchMode && keyCode == KeyEvent.KEYCODE_BACK
				&& TextUtils.isEmpty(getTextFilter()))
		{
			hideSoftKeyboard();
			onBackPressed();
			return true;
		}
		return false;
	}

	@Override
	protected void onListItemClick(ListView l, View v, int position, long id)
	{
		hideSoftKeyboard();

		if (mSearchMode && mAdapter.isSearchAllContactsItemPosition(position))
		{
			doSearch();
		} else if (mMode == MODE_INSERT_OR_EDIT_CONTACT
				|| mMode == MODE_QUERY_PICK_TO_EDIT)
		{
			Intent intent;
			if (position == 0 && !mSearchMode
					&& mMode != MODE_QUERY_PICK_TO_EDIT)
			{
				intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
			} else
			{
				intent = new Intent(Intent.ACTION_EDIT,
						getSelectedUri(position));
			}
			intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
			Bundle extras = getIntent().getExtras();
			if (extras != null)
			{
				intent.putExtras(extras);
			}
			intent.putExtra(KEY_PICKER_MODE,
					(mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER);

			startActivity(intent);
			finish();
		} else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
				&& position == 0)
		{
			Intent newContact = new Intent(Intents.Insert.ACTION,
					Contacts.CONTENT_URI);
			startActivityForResult(newContact, SUBACTIVITY_NEW_CONTACT);
		} else if (mMode == MODE_JOIN_CONTACT
				&& id == JOIN_MODE_SHOW_ALL_CONTACTS_ID)
		{
			mJoinModeShowAllContacts = false;
			startQuery();
		} else if (id > 0)
		{
			final Uri uri = getSelectedUri(position);
			if ((mMode & MODE_MASK_PICKER) == 0)
			{
				final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
				startActivityForResult(intent, SUBACTIVITY_VIEW_CONTACT);
			} else if (mMode == MODE_JOIN_CONTACT)
			{
				returnPickerResult(null, null, uri);
			} else if (mMode == MODE_QUERY_PICK_TO_VIEW)
			{
				// Started with query that should launch to view contact
				final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
				startActivity(intent);
				finish();
			} else if (mMode == MODE_PICK_PHONE
					|| mMode == MODE_QUERY_PICK_PHONE)
			{
				Cursor c = (Cursor) mAdapter.getItem(position);
				returnPickerResult(c, c
						.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX), uri);
			} else if ((mMode & MODE_MASK_PICKER) != 0)
			{
				Cursor c = (Cursor) mAdapter.getItem(position);
				returnPickerResult(c, c
						.getString(getSummaryDisplayNameColumnIndex()), uri);
			} else if (mMode == MODE_PICK_POSTAL
					|| mMode == MODE_LEGACY_PICK_POSTAL
					|| mMode == MODE_LEGACY_PICK_PHONE)
			{
				returnPickerResult(null, null, uri);
			}
		} else
		{
			signalError();
		}
	}

	private void hideSoftKeyboard()
	{
		// Hide soft keyboard, if visible
		InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
//		inputMethodManager.hideSoftInputFromWindow(mList.getWindowToken(), 0);
		inputMethodManager.hideSoftInputFromWindow(getListView().getWindowToken(), 0);
	}

	/**
	 * @param selectedUri
	 *            In most cases, this should be a lookup {@link Uri}, possibly
	 *            generated through {@link Contacts#getLookupUri(long, String)}.
	 */
	private void returnPickerResult(Cursor c, String name, Uri selectedUri)
	{
		final Intent intent = new Intent();

		if (mShortcutAction != null)
		{
			Intent shortcutIntent;
			if (Intent.ACTION_VIEW.equals(mShortcutAction))
			{
				// This is a simple shortcut to view a contact.
				shortcutIntent = new Intent(ACTION_QUICK_CONTACT);
				shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
						| Intent.FLAG_ACTIVITY_CLEAR_TOP
						| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

				shortcutIntent.setData(selectedUri);
				shortcutIntent.putExtra(EXTRA_MODE,
						ContactsContract.QuickContact.MODE_LARGE);
				shortcutIntent.putExtra(EXTRA_EXCLUDE_MIMES, (String[]) null);

				final Bitmap icon = framePhoto(loadContactPhoto(selectedUri,
						null));
				if (icon != null)
				{
					intent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
							scaleToAppIconSize(icon));
				} else
				{
					intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
							Intent.ShortcutIconResource.fromContext(this,
									R.drawable.ic_launcher_shortcut_contact));
				}
			} else
			{
				// This is a direct dial or sms shortcut.
				String number = c.getString(PHONE_NUMBER_COLUMN_INDEX);
				int type = c.getInt(PHONE_TYPE_COLUMN_INDEX);
				String scheme;
				int resid;
				if (Intent.ACTION_CALL.equals(mShortcutAction))
				{
					scheme = Constants.SCHEME_TEL;
					resid = R.drawable.badge_action_call;
				} else
				{
					scheme = Constants.SCHEME_SMSTO;
					resid = R.drawable.badge_action_sms;
				}

				// Make the URI a direct tel: URI so that it will always
				// continue to work
				Uri phoneUri = Uri.fromParts(scheme, number, null);
				shortcutIntent = new Intent(mShortcutAction, phoneUri);

				intent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
						generatePhoneNumberIcon(selectedUri, type, resid));
			}
			shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
			intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
			setResult(RESULT_OK, intent);
		} else
		{
			intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
			setResult(RESULT_OK, intent.setData(selectedUri));
		}
		finish();
	}

	private Bitmap framePhoto(Bitmap photo)
	{
		final Resources r = getResources();
		final Drawable frame = r
				.getDrawable(R.drawable.quickcontact_badge);

		final int width = r
				.getDimensionPixelSize(R.dimen.contact_shortcut_frame_width);
		final int height = r
				.getDimensionPixelSize(R.dimen.contact_shortcut_frame_height);

		frame.setBounds(0, 0, width, height);

		final Rect padding = new Rect();
		frame.getPadding(padding);

		final Rect source = new Rect(0, 0, photo.getWidth(), photo.getHeight());
		final Rect destination = new Rect(padding.left, padding.top, width
				- padding.right, height - padding.bottom);

		final int d = Math.max(width, height);
		final Bitmap b = Bitmap.createBitmap(d, d, Bitmap.Config.ARGB_8888);
		final Canvas c = new Canvas(b);

		c.translate((d - width) / 2.0f, (d - height) / 2.0f);
		frame.draw(c);
		c.drawBitmap(photo, source, destination, new Paint(
				Paint.FILTER_BITMAP_FLAG));

		return b;
	}

	/**
	 * Generates a phone number shortcut icon. Adds an overlay describing the
	 * type of the phone
	 * number, and if there is a photo also adds the call action icon.
	 * 
	 * @param lookupUri
	 *            The person the phone number belongs to
	 * @param type
	 *            The type of the phone number
	 * @param actionResId
	 *            The ID for the action resource
	 * @return The bitmap for the icon
	 */
	private Bitmap generatePhoneNumberIcon(Uri lookupUri, int type,
			int actionResId)
	{
		final Resources r = getResources();
		boolean drawPhoneOverlay = true;
		final float scaleDensity = getResources().getDisplayMetrics().scaledDensity;

		Bitmap photo = loadContactPhoto(lookupUri, null);
		if (photo == null)
		{
			// If there isn't a photo use the generic phone action icon instead
			Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
			if (phoneIcon != null)
			{
				photo = phoneIcon;
				drawPhoneOverlay = false;
			} else
			{
				return null;
			}
		}

		// Setup the drawing classes
		Bitmap icon = createShortcutBitmap();
		Canvas canvas = new Canvas(icon);

		// Copy in the photo
		Paint photoPaint = new Paint();
		photoPaint.setDither(true);
		photoPaint.setFilterBitmap(true);
		Rect src = new Rect(0, 0, photo.getWidth(), photo.getHeight());
		Rect dst = new Rect(0, 0, mIconSize, mIconSize);
		canvas.drawBitmap(photo, src, dst, photoPaint);

		// Create an overlay for the phone number type
		String overlay = null;
		switch (type)
		{
		case Phone.TYPE_HOME:
			overlay = getString(R.string.type_short_home);
			break;

		case Phone.TYPE_MOBILE:
			overlay = getString(R.string.type_short_mobile);
			break;

		case Phone.TYPE_WORK:
			overlay = getString(R.string.type_short_work);
			break;

		case Phone.TYPE_PAGER:
			overlay = getString(R.string.type_short_pager);
			break;

		case Phone.TYPE_OTHER:
			overlay = getString(R.string.type_short_other);
			break;
		}
		if (overlay != null)
		{
			Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG
					| Paint.DEV_KERN_TEXT_FLAG);
			textPaint.setTextSize(20.0f * scaleDensity);
			textPaint.setTypeface(Typeface.DEFAULT_BOLD);
			textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
			textPaint.setShadowLayer(3f, 1, 1, r
					.getColor(R.color.textColorIconOverlayShadow));
			canvas.drawText(overlay, 2 * scaleDensity, 16 * scaleDensity,
					textPaint);
		}

		// Draw the phone action icon as an overlay
		if (ENABLE_ACTION_ICON_OVERLAYS && drawPhoneOverlay)
		{
			Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
			if (phoneIcon != null)
			{
				src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
				int iconWidth = icon.getWidth();
				dst.set(iconWidth - ((int) (20 * scaleDensity)), -1, iconWidth,
						((int) (19 * scaleDensity)));
				canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
			}
		}

		return icon;
	}

	private Bitmap scaleToAppIconSize(Bitmap photo)
	{
		// Setup the drawing classes
		Bitmap icon = createShortcutBitmap();
		Canvas canvas = new Canvas(icon);

		// Copy in the photo
		Paint photoPaint = new Paint();
		photoPaint.setDither(true);
		photoPaint.setFilterBitmap(true);
		Rect src = new Rect(0, 0, photo.getWidth(), photo.getHeight());
		Rect dst = new Rect(0, 0, mIconSize, mIconSize);
		canvas.drawBitmap(photo, src, dst, photoPaint);

		return icon;
	}

	private Bitmap createShortcutBitmap()
	{
		return Bitmap.createBitmap(mIconSize, mIconSize,
				Bitmap.Config.ARGB_8888);
	}

	/**
	 * Returns the icon for the phone call action.
	 * 
	 * @param r
	 *            The resources to load the icon from
	 * @param resId
	 *            The resource ID to load
	 * @return the icon for the phone call action
	 */
	private Bitmap getPhoneActionIcon(Resources r, int resId)
	{
		Drawable phoneIcon = r.getDrawable(resId);
		if (phoneIcon instanceof BitmapDrawable)
		{
			BitmapDrawable bd = (BitmapDrawable) phoneIcon;
			return bd.getBitmap();
		} else
		{
			return null;
		}
	}

	private Bitmap loadContactPhoto(Uri selectedUri,
			BitmapFactory.Options options)
	{
		Uri contactUri = null;
		if (Contacts.CONTENT_ITEM_TYPE.equals(getContentResolver().getType(
				selectedUri)))
		{
			// TODO we should have a "photo" directory under the lookup URI
			// itself
			contactUri = Contacts.lookupContact(getContentResolver(),
					selectedUri);
		} else
		{

			Cursor cursor = getContentResolver().query(selectedUri,
					new String[] { Data.CONTACT_ID }, null, null, null);
			try
			{
				if (cursor != null && cursor.moveToFirst())
				{
					final long contactId = cursor.getLong(0);
					contactUri = ContentUris.withAppendedId(
							Contacts.CONTENT_URI, contactId);
				}
			} finally
			{
				if (cursor != null)
					cursor.close();
			}
		}

		Cursor cursor = null;
		Bitmap bm = null;

		try
		{
			Uri photoUri = Uri.withAppendedPath(contactUri,
					Contacts.Photo.CONTENT_DIRECTORY);
			cursor = getContentResolver().query(photoUri,
					new String[] { Photo.PHOTO }, null, null, null);
			if (cursor != null && cursor.moveToFirst())
			{
				bm = ContactsUtils.loadContactPhoto(cursor, 0, options);
			}
		} finally
		{
			if (cursor != null)
			{
				cursor.close();
			}
		}

		if (bm == null)
		{
			final int[] fallbacks = { R.drawable.ic_contact_picture,
					R.drawable.ic_contact_picture_2,
					R.drawable.ic_contact_picture_3 };
			bm = BitmapFactory.decodeResource(getResources(),
					fallbacks[new Random().nextInt(fallbacks.length)]);
		}

		return bm;
	}

	private boolean isSyncActive()
	{
		Account[] accounts = AccountManager.get(this).getAccounts();
		if (accounts != null && accounts.length > 0)
		{
//			IContentService contentService = ContentResolver.getContentService();
			for (Account account : accounts)
			{
//				try
//				{
//					if (contentService.isSyncActive(account,
//							ContactsContract.AUTHORITY))
//					{
//						return true;
//					}
//				} catch (RemoteException e)
//				{
//					Log.e(TAG, "Could not get the sync status");
//				}
				if (ContentResolver.isSyncActive(account,ContactsContract.AUTHORITY))
				{
					return true;
				}
			}
		}
		return false;
	}

	private void buildUserGroupUri(String group)
	{
		mGroupUri = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, group);
	}

	/**
	 * Sets the mode when the request is for "default"
	 */
	private void setDefaultMode()
	{
		// Load the preferences
		SharedPreferences prefs = PreferenceManager
				.getDefaultSharedPreferences(this);

		mDisplayOnlyPhones = prefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
				Prefs.DISPLAY_ONLY_PHONES_DEFAULT);
	}

	@Override
	protected void onDestroy()
	{
		super.onDestroy();
		mPhotoLoader.stop();
	}

	@Override
	protected void onPause()
	{
		super.onPause();
		unregisterProviderStatusObserver();
	}

	@Override
	protected void onResume()
	{
		super.onResume();

		registerProviderStatusObserver();
		mPhotoLoader.resume();

		Activity parent = getParent();

		// Do this before setting the filter. The filter thread relies
		// on some state that is initialized in setDefaultMode
		if (mMode == MODE_DEFAULT)
		{
			// If we're in default mode we need to possibly reset the mode due
			// to a change
			// in the preferences activity while we weren't running
			setDefaultMode();
		}

		// See if we were invoked with a filter
		if (mSearchMode)
		{
			mSearchEditText.requestFocus();
		}

		if (!mSearchMode && !checkProviderState(mJustCreated))
		{
			return;
		}

		if (mJustCreated)
		{
			// We need to start a query here the first time the activity is
			// launched, as long
			// as we aren't doing a filter.
			startQuery();
		}
		mJustCreated = false;
		mSearchInitiated = false;
	}

	@Override
	protected void onRestart()
	{
		super.onRestart();

		if (!checkProviderState(false))
		{
			return;
		}

		// The cursor was killed off in onStop(), so we need to get a new one
		// here
		// We do not perform the query if a filter is set on the list because
		// the
		// filter will cause the query to happen anyway
		if (TextUtils.isEmpty(getTextFilter()))
		{
			startQuery();
		} else
		{
			// Run the filtered query on the adapter
			mAdapter.onContentChanged();
		}
	}

	@Override
	protected void onStop()
	{
		super.onStop();

		mAdapter.setSuggestionsCursor(null);
		mAdapter.changeCursor(null);

		if (mMode == MODE_QUERY)
		{
			// Make sure the search box is closed
			SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
			searchManager.stopSearch();
		}
	}

	/**
	 * Obtains the contacts provider status and configures the UI accordingly.
	 * 
	 * @param loadData
	 *            true if the method needs to start a query when the
	 *            provider is in the normal state
	 * @return true if the provider status is normal
	 */
	private boolean checkProviderState(boolean loadData)
	{
		View importFailureView = findViewById(R.id.import_failure);
		if (importFailureView == null)
		{
			return true;
		}

		TextView messageView = (TextView) findViewById(R.id.emptyText);

		// This query can be performed on the UI thread because
		// the API explicitly allows such use.
		Cursor cursor = getContentResolver().query(ProviderStatus.CONTENT_URI,
				new String[] { ProviderStatus.STATUS, ProviderStatus.DATA1 },
				null, null, null);
		try
		{
			if (cursor.moveToFirst())
			{
				int status = cursor.getInt(0);
				if (status != mProviderStatus)
				{
					mProviderStatus = status;
					switch (status)
					{
					case ProviderStatus.STATUS_NORMAL:
						mAdapter.notifyDataSetInvalidated();
						if (loadData)
						{
							startQuery();
						}
						break;

					case ProviderStatus.STATUS_CHANGING_LOCALE:
						messageView.setText(R.string.locale_change_in_progress);
						mAdapter.changeCursor(null);
						mAdapter.notifyDataSetInvalidated();
						break;

					case ProviderStatus.STATUS_UPGRADING:
						messageView.setText(R.string.upgrade_in_progress);
						mAdapter.changeCursor(null);
						mAdapter.notifyDataSetInvalidated();
						break;

					case ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY:
						long size = cursor.getLong(1);
						String message = getResources().getString(
								R.string.upgrade_out_of_memory,
								new Object[] { size });
						messageView.setText(message);
						configureImportFailureView(importFailureView);
						mAdapter.changeCursor(null);
						mAdapter.notifyDataSetInvalidated();
						break;
					}
				}
			}
		} finally
		{
			cursor.close();
		}

		importFailureView
				.setVisibility(mProviderStatus == ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY ? View.VISIBLE
						: View.GONE);
		return mProviderStatus == ProviderStatus.STATUS_NORMAL;
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);

		// If Contacts was invoked by another Activity simply as a way of
		// picking a contact, don't show the options menu
		if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER)
		{
			return false;
		}

		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.list, menu);
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu)
	{
		final boolean defaultMode = (mMode == MODE_DEFAULT);
		menu.findItem(R.id.menu_display_groups).setVisible(defaultMode);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch (item.getItemId())
		{
		case R.id.menu_display_groups:
		{
			final Intent intent = new Intent(this,
					ContactsPreferencesActivity.class);
			startActivityForResult(intent, SUBACTIVITY_DISPLAY_GROUP);
			return true;
		}
		case R.id.menu_search:
		{
			onSearchRequested();
			return true;
		}
		case R.id.menu_add:
		{
			final Intent intent = new Intent(Intent.ACTION_INSERT,
					Contacts.CONTENT_URI);
			startActivity(intent);
			return true;
		}
		case R.id.menu_import_export:
		{
			displayImportExportDialog();
			return true;
		}
		case R.id.menu_accounts:
		{
			final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
			intent.putExtra(AUTHORITIES_FILTER_KEY,
					new String[] { ContactsContract.AUTHORITY });
			startActivity(intent);
			return true;
		}
		}
		return false;
	}

	@Override
	public void startSearch(String initialQuery, boolean selectInitialQuery,
			Bundle appSearchData, boolean globalSearch)
	{
		if (mProviderStatus != ProviderStatus.STATUS_NORMAL)
		{
			return;
		}

		if (globalSearch)
		{
			super.startSearch(initialQuery, selectInitialQuery, appSearchData,
					globalSearch);
		} else
		{
			if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0)
			{
				if ((mMode & MODE_MASK_PICKER) != 0)
				{
					ContactsSearchManager.startSearchForResult(this,
							initialQuery, SUBACTIVITY_FILTER);
				} else
				{
					ContactsSearchManager.startSearch(this, initialQuery);
				}
			}
		}
	}

	/**
	 * Performs filtering of the list based on the search query entered in the
	 * search text edit.
	 */
	protected void onSearchTextChanged()
	{
		// Set the proper empty string
		setEmptyText();

		Filter filter = mAdapter.getFilter();
		filter.filter(getTextFilter());
	}

	/**
	 * Starts a new activity that will run a search query and display search
	 * results.
	 */
	private void doSearch()
	{
		String query = getTextFilter();
		if (TextUtils.isEmpty(query))
		{
			return;
		}

		Intent intent = new Intent(this, SearchResultsActivity.class);
		Intent originalIntent = getIntent();
		Bundle originalExtras = originalIntent.getExtras();
		if (originalExtras != null)
		{
			intent.putExtras(originalExtras);
		}

		intent.putExtra(SearchManager.QUERY, query);
		if ((mMode & MODE_MASK_PICKER) != 0)
		{
			intent.setAction(ACTION_SEARCH_INTERNAL);
			intent.putExtra(SHORTCUT_ACTION_KEY, mShortcutAction);
			if (mShortcutAction != null)
			{
				if (Intent.ACTION_CALL.equals(mShortcutAction)
						|| Intent.ACTION_SENDTO.equals(mShortcutAction))
				{
					intent.putExtra(Insert.PHONE, query);
				}
			} else
			{
				switch (mQueryMode)
				{
				case QUERY_MODE_MAILTO:
					intent.putExtra(Insert.EMAIL, query);
					break;
				case QUERY_MODE_TEL:
					intent.putExtra(Insert.PHONE, query);
					break;
				}
			}
			startActivityForResult(intent, SUBACTIVITY_SEARCH);
		} else
		{
			intent.setAction(Intent.ACTION_SEARCH);
			startActivity(intent);
		}
	}

	@Override
	protected Dialog onCreateDialog(int id, Bundle bundle)
	{
		switch (id)
		{
		case R.string.import_from_sim:
		case R.string.import_from_sdcard:
		{
			return AccountSelectionUtil.getSelectAccountDialog(this, id);
		}
		case R.id.dialog_sdcard_not_found:
		{
			return new AlertDialog.Builder(this).setTitle(
					R.string.no_sdcard_title).setIcon(
					android.R.drawable.ic_dialog_alert).setMessage(
					R.string.no_sdcard_message).setPositiveButton(
					android.R.string.ok, null).create();
		}
		case R.id.dialog_delete_contact_confirmation:
		{
			return new AlertDialog.Builder(this).setTitle(
					R.string.deleteConfirmation_title).setIcon(
					android.R.drawable.ic_dialog_alert).setMessage(
					R.string.deleteConfirmation).setNegativeButton(
					android.R.string.cancel, null).setPositiveButton(
					android.R.string.ok, new DeleteClickListener()).create();
		}
		case R.id.dialog_readonly_contact_hide_confirmation:
		{
			return new AlertDialog.Builder(this).setTitle(
					R.string.deleteConfirmation_title).setIcon(
					android.R.drawable.ic_dialog_alert).setMessage(
					R.string.readOnlyContactWarning).setNegativeButton(
					android.R.string.cancel, null).setPositiveButton(
					android.R.string.ok, new DeleteClickListener()).create();
		}
		case R.id.dialog_readonly_contact_delete_confirmation:
		{
			return new AlertDialog.Builder(this).setTitle(
					R.string.deleteConfirmation_title).setIcon(
					android.R.drawable.ic_dialog_alert).setMessage(
					R.string.readOnlyContactDeleteConfirmation)
					.setNegativeButton(android.R.string.cancel, null)
					.setPositiveButton(android.R.string.ok,
							new DeleteClickListener()).create();
		}
		case R.id.dialog_multiple_contact_delete_confirmation:
		{
			return new AlertDialog.Builder(this).setTitle(
					R.string.deleteConfirmation_title).setIcon(
					android.R.drawable.ic_dialog_alert).setMessage(
					R.string.multipleContactDeleteConfirmation)
					.setNegativeButton(android.R.string.cancel, null)
					.setPositiveButton(android.R.string.ok,
							new DeleteClickListener()).create();
		}
		}
		return super.onCreateDialog(id, bundle);
	}

	/**
	 * Create a {@link Dialog} that allows the user to pick from a bulk import
	 * or bulk export task across all contacts.
	 */
	private void displayImportExportDialog()
	{
		// Wrap our context to inflate list items using correct theme
		final Context dialogContext = new ContextThemeWrapper(this,
				android.R.style.Theme_Light);
		final Resources res = dialogContext.getResources();
		final LayoutInflater dialogInflater = (LayoutInflater) dialogContext
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		// Adapter that shows a list of string resources
		final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this,
				android.R.layout.simple_list_item_1)
		{
			@Override
			public View getView(int position, View convertView, ViewGroup parent)
			{
				if (convertView == null)
				{
					convertView = dialogInflater.inflate(
							android.R.layout.simple_list_item_1, parent, false);
				}

				final int resId = this.getItem(position);
				((TextView) convertView).setText(resId);
				return convertView;
			}
		};

//		 if (TelephonyManager.getDefault().hasIccCard()) {
//		 adapter.add(R.string.import_from_sim);
//		 }
		if (res.getBoolean(R.bool.config_allow_import_from_sdcard))
		{
			adapter.add(R.string.import_from_sdcard);
		}
		if (res.getBoolean(R.bool.config_allow_export_to_sdcard))
		{
			adapter.add(R.string.export_to_sdcard);
		}
		if (res.getBoolean(R.bool.config_allow_share_visible_contacts))
		{
			adapter.add(R.string.share_visible_contacts);
		}

		final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener()
		{
			public void onClick(DialogInterface dialog, int which)
			{
				dialog.dismiss();

				final int resId = adapter.getItem(which);
				switch (resId)
				{
				case R.string.import_from_sim:
				case R.string.import_from_sdcard:
				{
					handleImportRequest(resId);
					break;
				}
				case R.string.export_to_sdcard:
				{
					Context context = ContactsActivity.this;
					Intent exportIntent = new Intent(context,
							ExportVCardActivity.class);
					context.startActivity(exportIntent);
					break;
				}
				case R.string.share_visible_contacts:
				{
					doShareVisibleContacts();
					break;
				}
				default:
				{
					Log.e(TAG, "Unexpected resource: "
							+ getResources().getResourceEntryName(resId));
				}
				}
			}
		};

		new AlertDialog.Builder(this).setTitle(R.string.dialog_import_export)
				.setNegativeButton(android.R.string.cancel, null)
				.setSingleChoiceItems(adapter, -1, clickListener).show();
	}

	private void doShareVisibleContacts()
	{
		final Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI,
				sLookupProjection, getContactSelection(), null, null);
		try
		{
			if (!cursor.moveToFirst())
			{
				Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT)
						.show();
				return;
			}

			StringBuilder uriListBuilder = new StringBuilder();
			int index = 0;
			for (; !cursor.isAfterLast(); cursor.moveToNext())
			{
				if (index != 0)
					uriListBuilder.append(':');
				uriListBuilder.append(cursor.getString(0));
				index++;
			}
			Uri uri = Uri.withAppendedPath(CONTENT_MULTI_VCARD_URI,
					Uri.encode(uriListBuilder.toString()));

			final Intent intent = new Intent(Intent.ACTION_SEND);
			intent.setType(Contacts.CONTENT_VCARD_TYPE);
			intent.putExtra(Intent.EXTRA_STREAM, uri);
			startActivity(intent);
		} finally
		{
			cursor.close();
		}
	}

	private void handleImportRequest(int resId)
	{
		// There's three possibilities:
		// - more than one accounts -> ask the user
		// - just one account -> use the account without asking the user
		// - no account -> use phone-local storage without asking the user
		final Sources sources = Sources.getInstance(this);
		final List<Account> accountList = sources.getAccounts(true);
		final int size = accountList.size();
		if (size > 1)
		{
			showDialog(resId);
			return;
		}

		AccountSelectionUtil.doImport(this, resId, (size == 1 ? accountList
				.get(0) : null));
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data)
	{
		switch (requestCode)
		{
		case SUBACTIVITY_NEW_CONTACT:
			if (resultCode == RESULT_OK)
			{
				returnPickerResult(null, data
						.getStringExtra(Intent.EXTRA_SHORTCUT_NAME), data
						.getData());
			}
			break;

		case SUBACTIVITY_VIEW_CONTACT:
			if (resultCode == RESULT_OK)
			{
				mAdapter.notifyDataSetChanged();
			}
			break;

		case SUBACTIVITY_DISPLAY_GROUP:
			// Mark as just created so we re-run the view query
			mJustCreated = true;
			break;

		case SUBACTIVITY_FILTER:
		case SUBACTIVITY_SEARCH:
			// Pass through results of filter or search UI
			if (resultCode == RESULT_OK)
			{
				setResult(RESULT_OK, data);
				finish();
			}
			break;
		}
	}

	@Override
	public void onCreateContextMenu(ContextMenu menu, View view,
			ContextMenuInfo menuInfo)
	{
		// If Contacts was invoked by another Activity simply as a way of
		// picking a contact, don't show the context menu
		if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER)
		{
			return;
		}

		AdapterView.AdapterContextMenuInfo info;
		try
		{
			info = (AdapterView.AdapterContextMenuInfo) menuInfo;
		} catch (ClassCastException e)
		{
			Log.e(TAG, "bad menuInfo", e);
			return;
		}

		Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
		if (cursor == null)
		{
			// For some reason the requested item isn't available, do nothing
			return;
		}
		long id = info.id;
		Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id);
		long rawContactId = ContactsUtils.queryForRawContactId(
				getContentResolver(), id);
		Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
				rawContactId);

		// Setup the menu header
		menu.setHeaderTitle(cursor
				.getString(getSummaryDisplayNameColumnIndex()));

		// View contact details
		menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
				.setIntent(new Intent(Intent.ACTION_VIEW, contactUri));

		if (cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0)
		{
			// Calling contact
			menu.add(0, MENU_ITEM_CALL, 0, getString(R.string.menu_call));
			// Send SMS item
			menu
					.add(0, MENU_ITEM_SEND_SMS, 0,
							getString(R.string.menu_sendSMS));
		}

		// Star toggling
		int starState = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
		if (starState == 0)
		{
			menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
		} else
		{
			menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_removeStar);
		}

		// Contact editing
		menu.add(0, MENU_ITEM_EDIT, 0, R.string.menu_editContact).setIntent(
				new Intent(Intent.ACTION_EDIT, rawContactUri));
		menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
	}

	@Override
	public boolean onContextItemSelected(MenuItem item)
	{
		AdapterView.AdapterContextMenuInfo info;
		try
		{
			info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
		} catch (ClassCastException e)
		{
			Log.e(TAG, "bad menuInfo", e);
			return false;
		}

		Cursor cursor = (Cursor) getListAdapter().getItem(info.position);

		switch (item.getItemId())
		{
		case MENU_ITEM_TOGGLE_STAR:
		{
			// Toggle the star
			ContentValues values = new ContentValues(1);
			values.put(Contacts.STARRED, cursor
					.getInt(SUMMARY_STARRED_COLUMN_INDEX) == 0 ? 1 : 0);
			final Uri selectedUri = this.getContactUri(info.position);
			getContentResolver().update(selectedUri, values, null, null);
			return true;
		}

		case MENU_ITEM_CALL:
		{
			callContact(cursor);
			return true;
		}

		case MENU_ITEM_SEND_SMS:
		{
			smsContact(cursor);
			return true;
		}

		case MENU_ITEM_DELETE:
		{
			doContactDelete(getContactUri(info.position));
			return true;
		}
		}

		return super.onContextItemSelected(item);
	}

	private void configureImportFailureView(View importFailureView)
	{

		OnClickListener listener = new OnClickListener()
		{

			public void onClick(View v)
			{
				switch (v.getId())
				{
				case R.id.import_failure_uninstall_apps:
				{
					startActivity(new Intent(
							Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
					break;
				}
				case R.id.import_failure_retry_upgrade:
				{
					// Send a provider status update, which will trigger a retry
					ContentValues values = new ContentValues();
					values.put(ProviderStatus.STATUS,
							ProviderStatus.STATUS_UPGRADING);
					getContentResolver().update(ProviderStatus.CONTENT_URI,
							values, null, null);
					break;
				}
				}
			}
		};

		Button uninstallApps = (Button) findViewById(R.id.import_failure_uninstall_apps);
		uninstallApps.setOnClickListener(listener);

		Button retryUpgrade = (Button) findViewById(R.id.import_failure_retry_upgrade);
		retryUpgrade.setOnClickListener(listener);
	}

	private String getTextFilter()
	{
		if (mSearchEditText != null)
		{
			return mSearchEditText.getText().toString();
		}
		return null;
	}

	@Override
	protected void onSaveInstanceState(Bundle icicle)
	{
		super.onSaveInstanceState(icicle);
		// Save list state in the bundle so we can restore it after the
		// QueryHandler has run
//		if (mList != null)
//		{
//			icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
//		}
		if (getListView() != null)
		{
			icicle.putParcelable(LIST_STATE_KEY, getListView().onSaveInstanceState());
		}
	}

	@Override
	protected void onRestoreInstanceState(Bundle icicle)
	{
		super.onRestoreInstanceState(icicle);
		// Retrieve list state. This will be applied after the QueryHandler has
		// run
		mListState = icicle.getParcelable(LIST_STATE_KEY);
	}

	/**
	 * Return the selection arguments for a default query based on the
	 * {@link #mDisplayOnlyPhones} flag.
	 */
	private String getContactSelection()
	{
		if (mDisplayOnlyPhones)
		{
			return CLAUSE_ONLY_VISIBLE + " AND " + CLAUSE_ONLY_PHONES;
		} else
		{
			return CLAUSE_ONLY_VISIBLE;
		}
	}

	/**
	 * Build the {@link Contacts#CONTENT_LOOKUP_URI} for the given
	 * {@link ListView} position, using {@link #mAdapter}.
	 */
	private Uri getContactUri(int position)
	{
		if (position == ListView.INVALID_POSITION)
		{
			throw new IllegalArgumentException("Position not in list bounds");
		}

		final Cursor cursor = (Cursor) mAdapter.getItem(position);
		if (cursor == null)
		{
			return null;
		}

		switch (mMode)
		{
		case MODE_LEGACY_PICK_PERSON:
		case MODE_LEGACY_PICK_OR_CREATE_PERSON:
		{
			final long personId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
			return ContentUris.withAppendedId(People.CONTENT_URI, personId);
		}

		default:
		{
			// Build and return soft, lookup reference
			final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
			final String lookupKey = cursor
					.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
			return Contacts.getLookupUri(contactId, lookupKey);
		}
		}
	}

	/**
	 * Build the {@link Uri} for the given {@link ListView} position, which can
	 * be used as result when in {@link #MODE_MASK_PICKER} mode.
	 */
	private Uri getSelectedUri(int position)
	{
		if (position == ListView.INVALID_POSITION)
		{
			throw new IllegalArgumentException("Position not in list bounds");
		}

		final long id = mAdapter.getItemId(position);
		switch (mMode)
		{
		case MODE_LEGACY_PICK_PERSON:
		case MODE_LEGACY_PICK_OR_CREATE_PERSON:
		{
			return ContentUris.withAppendedId(People.CONTENT_URI, id);
		}
		case MODE_PICK_PHONE:
		case MODE_QUERY_PICK_PHONE:
		{
			return ContentUris.withAppendedId(Data.CONTENT_URI, id);
		}
		case MODE_LEGACY_PICK_PHONE:
		{
			return ContentUris.withAppendedId(Phones.CONTENT_URI, id);
		}
		case MODE_PICK_POSTAL:
		{
			return ContentUris.withAppendedId(Data.CONTENT_URI, id);
		}
		case MODE_LEGACY_PICK_POSTAL:
		{
			return ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id);
		}
		default:
		{
			return getContactUri(position);
		}
		}
	}

	private Uri getContactFilterUri(String filter)
	{
		Uri baseUri;
		if (!TextUtils.isEmpty(filter))
		{
			baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri
					.encode(filter));
		} else
		{
			baseUri = Contacts.CONTENT_URI;
		}

		if (mAdapter.getDisplaySectionHeadersEnabled())
		{
			return buildSectionIndexerUri(baseUri);
		} else
		{
			return baseUri;
		}
	}

	private Uri getPeopleFilterUri(String filter)
	{
		if (!TextUtils.isEmpty(filter))
		{
			return Uri.withAppendedPath(People.CONTENT_FILTER_URI, Uri
					.encode(filter));
		} else
		{
			return People.CONTENT_URI;
		}
	}

	private static Uri buildSectionIndexerUri(Uri uri)
	{
		return uri.buildUpon().appendQueryParameter(ADDRESS_BOOK_INDEX_EXTRAS,
				"true").build();
	}

	private Uri getJoinSuggestionsUri(String filter)
	{
		Builder builder = Contacts.CONTENT_URI.buildUpon();
		builder.appendEncodedPath(String.valueOf(mQueryAggregateId));
		builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
		if (!TextUtils.isEmpty(filter))
		{
			builder.appendEncodedPath(Uri.encode(filter));
		}
		builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
		return builder.build();
	}

	String[] getProjectionForQuery()
	{
		switch (mMode)
		{
		case MODE_JOIN_CONTACT:
		case MODE_STREQUENT:
		case MODE_FREQUENT:
		case MODE_STARRED:
		case MODE_DEFAULT:
		case MODE_CUSTOM:
		case MODE_INSERT_OR_EDIT_CONTACT:
		case MODE_GROUP:
		case MODE_PICK_CONTACT:
		case MODE_PICK_OR_CREATE_CONTACT:
		{
			return mSearchMode ? CONTACTS_SUMMARY_FILTER_PROJECTION
					: CONTACTS_SUMMARY_PROJECTION;
		}
		case MODE_QUERY:
		case MODE_QUERY_PICK:
		case MODE_QUERY_PICK_TO_EDIT:
		{
			return CONTACTS_SUMMARY_FILTER_PROJECTION;
		}
		case MODE_LEGACY_PICK_PERSON:
		case MODE_LEGACY_PICK_OR_CREATE_PERSON:
		{
			return LEGACY_PEOPLE_PROJECTION;
		}
		case MODE_QUERY_PICK_PHONE:
		case MODE_PICK_PHONE:
		{
			return PHONES_PROJECTION;
		}
		case MODE_LEGACY_PICK_PHONE:
		{
			return LEGACY_PHONES_PROJECTION;
		}
		case MODE_PICK_POSTAL:
		{
			return POSTALS_PROJECTION;
		}
		case MODE_LEGACY_PICK_POSTAL:
		{
			return LEGACY_POSTALS_PROJECTION;
		}
		case MODE_QUERY_PICK_TO_VIEW:
		{
			if (mQueryMode == QUERY_MODE_MAILTO)
			{
				return CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL;
			} else if (mQueryMode == QUERY_MODE_TEL)
			{
				return PHONES_PROJECTION;
			}
			break;
		}
		}

		// Default to normal aggregate projection
		return CONTACTS_SUMMARY_PROJECTION;
	}

	private String getSortOrder(String[] projectionType)
	{
		if (mSortOrder == ContactsPreferences.SORT_ORDER_PRIMARY)
		{
			return SORT_KEY_PRIMARY;
		} else
		{
			return SORT_KEY_ALTERNATIVE;
		}
	}

	void startQuery()
	{
		// Set the proper empty string
		setEmptyText();

		if (mSearchResultsMode)
		{
			TextView foundContactsText = (TextView) findViewById(R.id.search_results_found);
			foundContactsText.setText(R.string.search_results_searching);
		}

		mAdapter.setLoading(true);

		// Cancel any pending queries
		mQueryHandler.cancelOperation(QUERY_TOKEN);
		mQueryHandler.setLoadingJoinSuggestions(false);

		mSortOrder = mContactsPrefs.getSortOrder();
		mDisplayOrder = mContactsPrefs.getDisplayOrder();

		// When sort order and display order contradict each other, we want to
		// highlight the part of the name used for sorting.
		mHighlightWhenScrolling = false;
		if (mSortOrder == ContactsPreferences.SORT_ORDER_PRIMARY
				&& mDisplayOrder == ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE)
		{
			mHighlightWhenScrolling = true;
		} else if (mSortOrder == ContactsPreferences.SORT_ORDER_ALTERNATIVE
				&& mDisplayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY)
		{
			mHighlightWhenScrolling = true;
		}

		String[] projection = getProjectionForQuery();
		if (mSearchMode && TextUtils.isEmpty(getTextFilter()))
		{
			mAdapter.changeCursor(new MatrixCursor(projection));
			return;
		}

		String callingPackage = getCallingPackage();
		Uri uri = getUriToQuery();
		if (!TextUtils.isEmpty(callingPackage))
		{
			uri = uri.buildUpon().appendQueryParameter(
					REQUESTING_PACKAGE_PARAM_KEY, callingPackage).build();
		}

		// Kick off the new query
		switch (mMode)
		{
		case MODE_GROUP:
		case MODE_DEFAULT:
		case MODE_CUSTOM:
		case MODE_PICK_CONTACT:
		case MODE_PICK_OR_CREATE_CONTACT:
		case MODE_INSERT_OR_EDIT_CONTACT:
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection,
					getContactSelection(), null, getSortOrder(projection));
			break;

		case MODE_LEGACY_PICK_PERSON:
		case MODE_LEGACY_PICK_OR_CREATE_PERSON:
		{
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null,
					null, People.DISPLAY_NAME);
			break;
		}
		case MODE_PICK_POSTAL:
		case MODE_QUERY:
		case MODE_QUERY_PICK:
		case MODE_QUERY_PICK_PHONE:
		case MODE_QUERY_PICK_TO_VIEW:
		case MODE_QUERY_PICK_TO_EDIT:
		{
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null,
					null, getSortOrder(projection));
			break;
		}

		case MODE_STARRED:
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection,
					Contacts.STARRED + "=1", null, getSortOrder(projection));
			break;

		case MODE_FREQUENT:
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection,
					Contacts.TIMES_CONTACTED + " > 0", null,
					Contacts.TIMES_CONTACTED + " DESC, "
							+ getSortOrder(projection));
			break;

		case MODE_STREQUENT:
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null,
					null, null);
			break;

		case MODE_PICK_PHONE:
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection,
					CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
			break;

		case MODE_LEGACY_PICK_PHONE:
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null,
					null, Phones.DISPLAY_NAME);
			break;

		case MODE_LEGACY_PICK_POSTAL:
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection,
					ContactMethods.KIND + "="
							+ android.provider.Contacts.KIND_POSTAL, null,
					ContactMethods.DISPLAY_NAME);
			break;

		case MODE_JOIN_CONTACT:
			mQueryHandler.setLoadingJoinSuggestions(true);
			mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null,
					null, null);
			break;
		}
	}

	private Uri getUriToQuery()
	{
		switch (mMode)
		{
		case MODE_JOIN_CONTACT:
			return getJoinSuggestionsUri(null);
		case MODE_FREQUENT:
		case MODE_STARRED:
			return Contacts.CONTENT_URI;

		case MODE_DEFAULT:
		case MODE_CUSTOM:
		case MODE_INSERT_OR_EDIT_CONTACT:
		case MODE_PICK_CONTACT:
		case MODE_PICK_OR_CREATE_CONTACT:
		{
			return CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS;
		}
		case MODE_STREQUENT:
		{
			return Contacts.CONTENT_STREQUENT_URI;
		}
		case MODE_LEGACY_PICK_PERSON:
		case MODE_LEGACY_PICK_OR_CREATE_PERSON:
		{
			return People.CONTENT_URI;
		}
		case MODE_PICK_PHONE:
		{
			return buildSectionIndexerUri(Phone.CONTENT_URI);
		}
		case MODE_LEGACY_PICK_PHONE:
		{
			return Phones.CONTENT_URI;
		}
		case MODE_PICK_POSTAL:
		{
			return buildSectionIndexerUri(StructuredPostal.CONTENT_URI);
		}
		case MODE_LEGACY_PICK_POSTAL:
		{
			return ContactMethods.CONTENT_URI;
		}
		case MODE_QUERY_PICK_TO_VIEW:
		{
			if (mQueryMode == QUERY_MODE_MAILTO)
			{
				return Uri.withAppendedPath(Email.CONTENT_FILTER_URI, Uri
						.encode(mInitialFilter));
			} else if (mQueryMode == QUERY_MODE_TEL)
			{
				return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri
						.encode(mInitialFilter));
			}
			return CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS;
		}
		case MODE_QUERY:
		case MODE_QUERY_PICK:
		case MODE_QUERY_PICK_TO_EDIT:
		{
			return getContactFilterUri(mInitialFilter);
		}
		case MODE_QUERY_PICK_PHONE:
		{
			return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri
					.encode(mInitialFilter));
		}
		case MODE_GROUP:
		{
			return mGroupUri;
		}
		default:
		{
			throw new IllegalStateException(
					"Can't generate URI: Unsupported Mode.");
		}
		}
	}

	private Cursor getShowAllContactsLabelCursor(String[] projection)
	{
		MatrixCursor matrixCursor = new MatrixCursor(projection);
		Object[] row = new Object[projection.length];
		// The only columns we care about is the id
		row[SUMMARY_ID_COLUMN_INDEX] = JOIN_MODE_SHOW_ALL_CONTACTS_ID;
		matrixCursor.addRow(row);
		return matrixCursor;
	}

	// TODO: fix PluralRules to handle zero correctly and use
	// Resources.getQuantityText directly
	protected String getQuantityText(int count, int zeroResourceId,
			int pluralResourceId)
	{
		if (count == 0)
		{
			return getString(zeroResourceId);
		} else
		{
			String format = getResources().getQuantityText(pluralResourceId,
					count).toString();
			return String.format(format, count);
		}
	}

	/**
	 * Called from a background thread to do the filter and return the resulting
	 * cursor.
	 * 
	 * @param filter
	 *            the text that was entered to filter on
	 * @return a cursor with the results of the filter
	 */
	Cursor doFilter(String filter)
	{
		String[] projection = getProjectionForQuery();
		if (mSearchMode && TextUtils.isEmpty(getTextFilter()))
		{
			return new MatrixCursor(projection);
		}

		final ContentResolver resolver = getContentResolver();
		switch (mMode)
		{
		case MODE_DEFAULT:
		case MODE_CUSTOM:
		case MODE_PICK_CONTACT:
		case MODE_PICK_OR_CREATE_CONTACT:
		case MODE_INSERT_OR_EDIT_CONTACT:
		{
			return resolver.query(getContactFilterUri(filter), projection,
					getContactSelection(), null, getSortOrder(projection));
		}

		case MODE_LEGACY_PICK_PERSON:
		case MODE_LEGACY_PICK_OR_CREATE_PERSON:
		{
			return resolver.query(getPeopleFilterUri(filter), projection, null,
					null, People.DISPLAY_NAME);
		}

		case MODE_STARRED:
		{
			return resolver.query(getContactFilterUri(filter), projection,
					Contacts.STARRED + "=1", null, getSortOrder(projection));
		}

		case MODE_FREQUENT:
		{
			return resolver.query(getContactFilterUri(filter), projection,
					Contacts.TIMES_CONTACTED + " > 0", null,
					Contacts.TIMES_CONTACTED + " DESC, "
							+ getSortOrder(projection));
		}

		case MODE_STREQUENT:
		{
			Uri uri;
			if (!TextUtils.isEmpty(filter))
			{
				uri = Uri.withAppendedPath(
						Contacts.CONTENT_STREQUENT_FILTER_URI, Uri
								.encode(filter));
			} else
			{
				uri = Contacts.CONTENT_STREQUENT_URI;
			}
			return resolver.query(uri, projection, null, null, null);
		}

		case MODE_PICK_PHONE:
		{
			Uri uri = getUriToQuery();
			if (!TextUtils.isEmpty(filter))
			{
				uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri
						.encode(filter));
			}
			return resolver.query(uri, projection, CLAUSE_ONLY_VISIBLE, null,
					getSortOrder(projection));
		}

		case MODE_LEGACY_PICK_PHONE:
		{
			// TODO: Support filtering here (bug 2092503)
			break;
		}

		case MODE_JOIN_CONTACT:
		{

			// We are on a background thread. Run queries one after the other
			// synchronously
			Cursor cursor = resolver.query(getJoinSuggestionsUri(filter),
					projection, null, null, null);
			mAdapter.setSuggestionsCursor(cursor);
			mJoinModeShowAllContacts = false;
			return resolver.query(getContactFilterUri(filter), projection,
					Contacts._ID + " != " + mQueryAggregateId + " AND "
							+ CLAUSE_ONLY_VISIBLE, null,
					getSortOrder(projection));
		}
		}
		throw new UnsupportedOperationException(
				"filtering not allowed in mode " + mMode);
	}

	/**
	 * Calls the currently selected list item.
	 * 
	 * @return true if the call was initiated, false otherwise
	 */
	boolean callSelection()
	{
		ListView list = getListView();
		if (list.hasFocus())
		{
			Cursor cursor = (Cursor) list.getSelectedItem();
			return callContact(cursor);
		}
		return false;
	}

	boolean callContact(Cursor cursor)
	{
		return callOrSmsContact(cursor, false /* call */);
	}

	boolean smsContact(Cursor cursor)
	{
		return callOrSmsContact(cursor, true /* sms */);
	}

	/**
	 * Calls the contact which the cursor is point to.
	 * 
	 * @return true if the call was initiated, false otherwise
	 */
	boolean callOrSmsContact(Cursor cursor, boolean sendSms)
	{
		if (cursor == null)
		{
			return false;
		}

		switch (mMode)
		{
		case MODE_PICK_PHONE:
		case MODE_LEGACY_PICK_PHONE:
		case MODE_QUERY_PICK_PHONE:
		{
			String phone = cursor.getString(PHONE_NUMBER_COLUMN_INDEX);
			if (sendSms)
			{
				ContactsUtils.initiateSms(this, phone);
			} else
			{
				ContactsUtils.initiateCall(this, phone);
			}
			return true;
		}

		case MODE_PICK_POSTAL:
		case MODE_LEGACY_PICK_POSTAL:
		{
			return false;
		}

		default:
		{

			boolean hasPhone = cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
			if (!hasPhone)
			{
				// There is no phone number.
				signalError();
				return false;
			}

			String phone = null;
			Cursor phonesCursor = null;
			phonesCursor = queryPhoneNumbers(cursor
					.getLong(SUMMARY_ID_COLUMN_INDEX));
			if (phonesCursor == null || phonesCursor.getCount() == 0)
			{
				// No valid number
				signalError();
				return false;
			} else if (phonesCursor.getCount() == 1)
			{
				// only one number, call it.
				phone = phonesCursor.getString(phonesCursor
						.getColumnIndex(Phone.NUMBER));
			} else
			{
				phonesCursor.moveToPosition(-1);
				while (phonesCursor.moveToNext())
				{
					if (phonesCursor.getInt(phonesCursor
							.getColumnIndex(Phone.IS_SUPER_PRIMARY)) != 0)
					{
						// Found super primary, call it.
						phone = phonesCursor.getString(phonesCursor
								.getColumnIndex(Phone.NUMBER));
						break;
					}
				}
			}

			if (phone == null)
			{
				// Display dialog to choose a number to call.
				PhoneDisambigDialog phoneDialog = new PhoneDisambigDialog(this,
						phonesCursor, sendSms);
				phoneDialog.show();
			} else
			{
				if (sendSms)
				{
					ContactsUtils.initiateSms(this, phone);
				} else
				{
					ContactsUtils.initiateCall(this, phone);
				}
			}
		}
		}
		return true;
	}

	private Cursor queryPhoneNumbers(long contactId)
	{
		Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
				contactId);
		Uri dataUri = Uri.withAppendedPath(baseUri,
				Contacts.Data.CONTENT_DIRECTORY);

		Cursor c = getContentResolver().query(
				dataUri,
				new String[] { Phone._ID, Phone.NUMBER, Phone.IS_SUPER_PRIMARY,
						RawContacts.ACCOUNT_TYPE, Phone.TYPE, Phone.LABEL },
				Data.MIMETYPE + "=?", new String[] { Phone.CONTENT_ITEM_TYPE },
				null);
		if (c != null && c.moveToFirst())
		{
			return c;
		}
		return null;
	}

	/**
	 * Signal an error to the user.
	 */
	void signalError()
	{
		// TODO play an error beep or something...
	}

	Cursor getItemForView(View view)
	{
		ListView listView = getListView();
		int index = listView.getPositionForView(view);
		if (index < 0)
		{
			return null;
		}
		return (Cursor) listView.getAdapter().getItem(index);
	}

	private static class QueryHandler extends AsyncQueryHandler
	{
		protected final WeakReference<ContactsActivity> mActivity;
		protected boolean mLoadingJoinSuggestions = false;

		public QueryHandler(Context context)
		{
			super(context.getContentResolver());
			mActivity = new WeakReference<ContactsActivity>(
					(ContactsActivity) context);
		}

		public void setLoadingJoinSuggestions(boolean flag)
		{
			mLoadingJoinSuggestions = flag;
		}

		@Override
		protected void onQueryComplete(int token, Object cookie, Cursor cursor)
		{
			final ContactsActivity activity = mActivity.get();
			if (activity != null && !activity.isFinishing())
			{

				// Whenever we get a suggestions cursor, we need to immediately
				// kick off
				// another query for the complete list of contacts
				if (cursor != null && mLoadingJoinSuggestions)
				{
					mLoadingJoinSuggestions = false;
					if (cursor.getCount() > 0)
					{
						activity.mAdapter.setSuggestionsCursor(cursor);
					} else
					{
						cursor.close();
						activity.mAdapter.setSuggestionsCursor(null);
					}

					if (activity.mAdapter.mSuggestionsCursorCount == 0
							|| !activity.mJoinModeShowAllContacts)
					{
						startQuery(
								QUERY_TOKEN,
								null,
								activity.getContactFilterUri(activity
										.getTextFilter()),
								CONTACTS_SUMMARY_PROJECTION,
								Contacts._ID + " != "
										+ activity.mQueryAggregateId + " AND "
										+ CLAUSE_ONLY_VISIBLE,
								null,
								activity
										.getSortOrder(CONTACTS_SUMMARY_PROJECTION));
						return;
					}

					cursor = activity
							.getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
				}

				activity.mAdapter.changeCursor(cursor);

				// Now that the cursor is populated again, it's possible to
				// restore the list state
				if (activity.mListState != null)
				{
//					activity.mList.onRestoreInstanceState(activity.mListState);
					activity.getListView().onRestoreInstanceState(activity.mListState);  
					activity.mListState = null;
				}
			} else
			{
				if (cursor != null)
				{
					cursor.close();
				}
			}
		}
	}

	final static class ContactListItemCache
	{
		public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
		public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
		public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
		public TextWithHighlighting textWithHighlighting;
		public CharArrayBuffer phoneticNameBuffer = new CharArrayBuffer(128);
	}

	final static class PinnedHeaderCache
	{
		public TextView titleView;
		public ColorStateList textColor;
		public Drawable background;
	}

	private final class ContactItemListAdapter extends CursorAdapter implements
			SectionIndexer, OnScrollListener,
			PinnedHeaderListView.PinnedHeaderAdapter
	{
		private SectionIndexer mIndexer;
		private boolean mLoading = true;
		private CharSequence mUnknownNameText;
		private boolean mDisplayPhotos = false;
		private boolean mDisplayCallButton = false;
		private boolean mDisplayAdditionalData = true;
		private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
		private boolean mDisplaySectionHeaders = true;
		private Cursor mSuggestionsCursor;
		private int mSuggestionsCursorCount;
		private Context mContext;

		public ContactItemListAdapter(Context context)
		{
			super(context, null, false);
			mContext = context;
			
			mUnknownNameText = context.getText(android.R.string.unknownName);
			switch (mMode)
			{
			case MODE_LEGACY_PICK_POSTAL:
			case MODE_PICK_POSTAL:
			case MODE_LEGACY_PICK_PHONE:
			case MODE_PICK_PHONE:
			case MODE_STREQUENT:
			case MODE_FREQUENT:
				mDisplaySectionHeaders = false;
				break;
			}

			if (mSearchMode)
			{
				mDisplaySectionHeaders = false;
			}

			// Do not display the second line of text if in a specific SEARCH
			// query mode, usually for
			// matching a specific E-mail or phone number. Any contact details
			// shown would be identical, and columns might not even be present
			// in the returned cursor.
			if (mMode != MODE_QUERY_PICK_PHONE && mQueryMode != QUERY_MODE_NONE)
			{
				mDisplayAdditionalData = false;
			}

			if ((mMode & MODE_MASK_NO_DATA) == MODE_MASK_NO_DATA)
			{
				mDisplayAdditionalData = false;
			}

			if ((mMode & MODE_MASK_SHOW_CALL_BUTTON) == MODE_MASK_SHOW_CALL_BUTTON)
			{
				mDisplayCallButton = true;
			}

			if ((mMode & MODE_MASK_SHOW_PHOTOS) == MODE_MASK_SHOW_PHOTOS)
			{
				mDisplayPhotos = true;
			}
		}

		public boolean getDisplaySectionHeadersEnabled()
		{
			return mDisplaySectionHeaders;
		}

		public void setSuggestionsCursor(Cursor cursor)
		{
			if (mSuggestionsCursor != null)
			{
				mSuggestionsCursor.close();
			}
			mSuggestionsCursor = cursor;
			mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
		}

		/**
		 * Callback on the UI thread when the content observer on the backing
		 * cursor fires.
		 * Instead of calling requery we need to do an async query so that the
		 * requery doesn't
		 * block the UI thread for a long time.
		 */
		@Override
		protected void onContentChanged()
		{
			CharSequence constraint = getTextFilter();
			if (!TextUtils.isEmpty(constraint))
			{
				// Reset the filter state then start an async filter operation
				Filter filter = getFilter();
				filter.filter(constraint);
			} else
			{
				// Start an async query
				startQuery();
			}
		}

		public void setLoading(boolean loading)
		{
			mLoading = loading;
		}

		@Override
		public boolean isEmpty()
		{
			if (mProviderStatus != ProviderStatus.STATUS_NORMAL)
			{
				return true;
			}

			if (mSearchMode)
			{
				return TextUtils.isEmpty(getTextFilter());
			} else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW)
			{
				// This mode mask adds a header and we always want it to show
				// up, even
				// if the list is empty, so always claim the list is not empty.
				return false;
			} else
			{
//				if (mCursor == null || mLoading)
				if (mAdapter.getCursor() == null || mLoading)
				{
					// We don't want the empty state to show when loading.
					return false;
				} else
				{
					return super.isEmpty();
				}
			}
		}

		@Override
		public int getItemViewType(int position)
		{
			if (position == 0
					&& (mShowNumberOfContacts || (mMode & MODE_MASK_CREATE_NEW) != 0))
			{
				return IGNORE_ITEM_VIEW_TYPE;
			}

			if (isShowAllContactsItemPosition(position))
			{
				return IGNORE_ITEM_VIEW_TYPE;
			}

			if (isSearchAllContactsItemPosition(position))
			{
				return IGNORE_ITEM_VIEW_TYPE;
			}

			if (getSeparatorId(position) != 0)
			{
				// We don't want the separator view to be recycled.
				return IGNORE_ITEM_VIEW_TYPE;
			}

			return super.getItemViewType(position);
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent)
		{
//			if (!mDataValid)
//			{
//				throw new IllegalStateException(
//						"this should only be called when the cursor is valid");
//			}

			// handle the total contacts item
			if (position == 0 && mShowNumberOfContacts)
			{
				return getTotalContactCountView(parent);
			}

			if (position == 0 && (mMode & MODE_MASK_CREATE_NEW) != 0)
			{
				// Add the header for creating a new contact
				return getLayoutInflater().inflate(R.layout.create_new_contact,
						parent, false);
			}

			if (isShowAllContactsItemPosition(position))
			{
				return getLayoutInflater().inflate(
						R.layout.contacts_list_show_all_item, parent, false);
			}

			if (isSearchAllContactsItemPosition(position))
			{
				return getLayoutInflater().inflate(
						R.layout.contacts_list_search_all_item, parent, false);
			}

			// Handle the separator specially
			int separatorId = getSeparatorId(position);
			if (separatorId != 0)
			{
				TextView view = (TextView) getLayoutInflater().inflate(
						R.layout.list_separator, parent, false);
				view.setText(separatorId);
				return view;
			}

			boolean showingSuggestion;
			Cursor cursor;
			if (mSuggestionsCursorCount != 0
					&& position < mSuggestionsCursorCount + 2)
			{
				showingSuggestion = true;
				cursor = mSuggestionsCursor;
			} else
			{
				showingSuggestion = false;
//				cursor = mCursor;
				cursor = (Cursor)mAdapter.getItem(position);
			}

			int realPosition = getRealPosition(position);
			if (!cursor.moveToPosition(realPosition))
			{
				throw new IllegalStateException(
						"couldn't move cursor to position " + position);
			}

			boolean newView;
			View v;
			if (convertView == null || convertView.getTag() == null)
			{
				newView = true;
				v = newView(mContext, cursor, parent);
			} else
			{
				newView = false;
				v = convertView;
			}
			bindView(v, mContext, cursor);
			bindSectionHeader(v, realPosition, mDisplaySectionHeaders
					&& !showingSuggestion);
			return v;
		}

		private View getTotalContactCountView(ViewGroup parent)
		{
			final LayoutInflater inflater = getLayoutInflater();
			View view = inflater
					.inflate(R.layout.total_contacts, parent, false);

			TextView totalContacts = (TextView) view
					.findViewById(R.id.totalContactsText);

			String text;
			int count = getRealCount();

			if (mSearchMode && !TextUtils.isEmpty(getTextFilter()))
			{
				text = getQuantityText(count,
						R.string.listFoundAllContactsZero,
						R.plurals.searchFoundContacts);
			} else
			{
				if (mDisplayOnlyPhones)
				{
					text = getQuantityText(count,
							R.string.listTotalPhoneContactsZero,
							R.plurals.listTotalPhoneContacts);
				} else
				{
					text = getQuantityText(count,
							R.string.listTotalAllContactsZero,
							R.plurals.listTotalAllContacts);
				}
			}
			totalContacts.setText(text);
			return view;
		}

		private boolean isShowAllContactsItemPosition(int position)
		{
			return mMode == MODE_JOIN_CONTACT && mJoinModeShowAllContacts
					&& mSuggestionsCursorCount != 0
					&& position == mSuggestionsCursorCount + 2;
		}

		private boolean isSearchAllContactsItemPosition(int position)
		{
			return mSearchMode && position == getCount() - 1;
		}

		private int getSeparatorId(int position)
		{
			int separatorId = 0;
			if (position == mFrequentSeparatorPos)
			{
				separatorId = R.string.favoritesFrquentSeparator;
			}
			if (mSuggestionsCursorCount != 0)
			{
				if (position == 0)
				{
					separatorId = R.string.separatorJoinAggregateSuggestions;
				} else if (position == mSuggestionsCursorCount + 1)
				{
					separatorId = R.string.separatorJoinAggregateAll;
				}
			}
			return separatorId;
		}

		@Override
		public View newView(Context context, Cursor cursor, ViewGroup parent)
		{
			final ContactListItemView view = new ContactListItemView(context,
					null);
			view.setOnCallButtonClickListener(ContactsActivity.this);
			view.setTag(new ContactListItemCache());
			return view;
		}

		@Override
		public void bindView(View itemView, Context context, Cursor cursor)
		{
			final ContactListItemView view = (ContactListItemView) itemView;
			final ContactListItemCache cache = (ContactListItemCache) view
					.getTag();

			int typeColumnIndex;
			int dataColumnIndex;
			int labelColumnIndex;
			int defaultType;
			int nameColumnIndex;
			int phoneticNameColumnIndex;
			boolean displayAdditionalData = mDisplayAdditionalData;
			boolean highlightingEnabled = false;
			switch (mMode)
			{
			case MODE_PICK_PHONE:
			case MODE_LEGACY_PICK_PHONE:
			case MODE_QUERY_PICK_PHONE:
			{
				nameColumnIndex = PHONE_DISPLAY_NAME_COLUMN_INDEX;
				phoneticNameColumnIndex = -1;
				dataColumnIndex = PHONE_NUMBER_COLUMN_INDEX;
				typeColumnIndex = PHONE_TYPE_COLUMN_INDEX;
				labelColumnIndex = PHONE_LABEL_COLUMN_INDEX;
				defaultType = Phone.TYPE_HOME;
				break;
			}
			case MODE_PICK_POSTAL:
			case MODE_LEGACY_PICK_POSTAL:
			{
				nameColumnIndex = POSTAL_DISPLAY_NAME_COLUMN_INDEX;
				phoneticNameColumnIndex = -1;
				dataColumnIndex = POSTAL_ADDRESS_COLUMN_INDEX;
				typeColumnIndex = POSTAL_TYPE_COLUMN_INDEX;
				labelColumnIndex = POSTAL_LABEL_COLUMN_INDEX;
				defaultType = StructuredPostal.TYPE_HOME;
				break;
			}
			default:
			{
				nameColumnIndex = getSummaryDisplayNameColumnIndex();
				if (mMode == MODE_LEGACY_PICK_PERSON
						|| mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON)
				{
					phoneticNameColumnIndex = -1;
				} else
				{
					phoneticNameColumnIndex = SUMMARY_PHONETIC_NAME_COLUMN_INDEX;
				}
				dataColumnIndex = -1;
				typeColumnIndex = -1;
				labelColumnIndex = -1;
				defaultType = Phone.TYPE_HOME;
				displayAdditionalData = false;
				highlightingEnabled = mHighlightWhenScrolling
						&& mMode != MODE_STREQUENT;
			}
			}

			// Set the name
			cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
			TextView nameView = view.getNameTextView();
			int size = cache.nameBuffer.sizeCopied;
			if (size != 0)
			{
				if (highlightingEnabled)
				{
					if (cache.textWithHighlighting == null)
					{
						cache.textWithHighlighting = mHighlightingAnimation
								.createTextWithHighlighting();
					}
					buildDisplayNameWithHighlighting(nameView, cursor,
							cache.nameBuffer, cache.highlightedTextBuffer,
							cache.textWithHighlighting);
				} else
				{
					nameView.setText(cache.nameBuffer.data, 0, size);
				}
			} else
			{
				nameView.setText(mUnknownNameText);
			}

			boolean hasPhone = cursor.getColumnCount() >= SUMMARY_HAS_PHONE_COLUMN_INDEX
					&& cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;

			// Make the call button visible if requested.
			if (mDisplayCallButton && hasPhone)
			{
				int pos = cursor.getPosition();
				view.showCallButton(android.R.id.button1, pos);
			} else
			{
				view.hideCallButton();
			}

			// Set the photo, if requested
			if (mDisplayPhotos)
			{
				boolean useQuickContact = (mMode & MODE_MASK_DISABLE_QUIKCCONTACT) == 0;

				long photoId = 0;
				if (!cursor.isNull(SUMMARY_PHOTO_ID_COLUMN_INDEX))
				{
					photoId = cursor.getLong(SUMMARY_PHOTO_ID_COLUMN_INDEX);
				}

				ImageView viewToUse;
				if (useQuickContact)
				{
					// Build soft lookup reference
					final long contactId = cursor
							.getLong(SUMMARY_ID_COLUMN_INDEX);
					final String lookupKey = cursor
							.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
					QuickContactBadge quickContact = view.getQuickContact();
					quickContact.assignContactUri(Contacts.getLookupUri(
							contactId, lookupKey));
					viewToUse = quickContact;
				} else
				{
					viewToUse = view.getPhotoView();
				}

				final int position = cursor.getPosition();
				mPhotoLoader.loadPhoto(viewToUse, photoId);
			}

			if ((mMode & MODE_MASK_NO_PRESENCE) == 0)
			{
				// Set the proper icon (star or presence or nothing)
				int serverStatus;
				if (!cursor.isNull(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX))
				{
					serverStatus = cursor
							.getInt(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
					Drawable icon = ContactPresenceIconUtil.getPresenceIcon(
							mContext, serverStatus);
					if (icon != null)
					{
						view.setPresence(icon);
					} else
					{
						view.setPresence(null);
					}
				} else
				{
					view.setPresence(null);
				}
			} else
			{
				view.setPresence(null);
			}

			if (mShowSearchSnippets)
			{
				boolean showSnippet = false;
				String snippetMimeType = cursor
						.getString(SUMMARY_DATA_MIMETYPE_COLUMN_INDEX);
				if (Email.CONTENT_ITEM_TYPE.equals(snippetMimeType))
				{
					String email = cursor
							.getString(SUMMARY_DATA_DATA1_COLUMN_INDEX);
					if (!TextUtils.isEmpty(email))
					{
						view.setSnippet(email);
						showSnippet = true;
					}
				} else if (Organization.CONTENT_ITEM_TYPE
						.equals(snippetMimeType))
				{
					String company = cursor
							.getString(SUMMARY_DATA_DATA1_COLUMN_INDEX);
					String title = cursor
							.getString(SUMMARY_DATA_DATA4_COLUMN_INDEX);
					if (!TextUtils.isEmpty(company))
					{
						if (!TextUtils.isEmpty(title))
						{
							view.setSnippet(company + " / " + title);
						} else
						{
							view.setSnippet(company);
						}
						showSnippet = true;
					} else if (!TextUtils.isEmpty(title))
					{
						view.setSnippet(title);
						showSnippet = true;
					}
				} else if (Nickname.CONTENT_ITEM_TYPE.equals(snippetMimeType))
				{
					String nickname = cursor
							.getString(SUMMARY_DATA_DATA1_COLUMN_INDEX);
					if (!TextUtils.isEmpty(nickname))
					{
						view.setSnippet(nickname);
						showSnippet = true;
					}
				}

				if (!showSnippet)
				{
					view.setSnippet(null);
				}
			}

			if (!displayAdditionalData)
			{
				if (phoneticNameColumnIndex != -1)
				{

					// Set the name
					cursor.copyStringToBuffer(phoneticNameColumnIndex,
							cache.phoneticNameBuffer);
					int phoneticNameSize = cache.phoneticNameBuffer.sizeCopied;
					if (phoneticNameSize != 0)
					{
						view.setLabel(cache.phoneticNameBuffer.data,
								phoneticNameSize);
					} else
					{
						view.setLabel(null);
					}
				} else
				{
					view.setLabel(null);
				}
				return;
			}

			// Set the data.
			cursor.copyStringToBuffer(dataColumnIndex, cache.dataBuffer);

			size = cache.dataBuffer.sizeCopied;
			view.setData(cache.dataBuffer.data, size);

			// Set the label.
			if (!cursor.isNull(typeColumnIndex))
			{
				final int type = cursor.getInt(typeColumnIndex);
				final String label = cursor.getString(labelColumnIndex);

				if (mMode == MODE_LEGACY_PICK_POSTAL
						|| mMode == MODE_PICK_POSTAL)
				{
					// TODO cache
					view.setLabel(StructuredPostal.getTypeLabel(context
							.getResources(), type, label));
				} else
				{
					// TODO cache
					view.setLabel(Phone.getTypeLabel(context.getResources(),
							type, label));
				}
			} else
			{
				view.setLabel(null);
			}
		}

		/**
		 * Computes the span of the display name that has highlighted parts and
		 * configures
		 * the display name text view accordingly.
		 */
		private void buildDisplayNameWithHighlighting(TextView textView,
				Cursor cursor, CharArrayBuffer buffer1,
				CharArrayBuffer buffer2,
				TextWithHighlighting textWithHighlighting)
		{
			int oppositeDisplayOrderColumnIndex;
			if (mDisplayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY)
			{
				oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
			} else
			{
				oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
			}
			cursor.copyStringToBuffer(oppositeDisplayOrderColumnIndex, buffer2);

			textWithHighlighting.setText(buffer1, buffer2);
			textView.setText(textWithHighlighting);
		}

		private void bindSectionHeader(View itemView, int position,
				boolean displaySectionHeaders)
		{
			final ContactListItemView view = (ContactListItemView) itemView;
			final ContactListItemCache cache = (ContactListItemCache) view
					.getTag();
			if (!displaySectionHeaders)
			{
				view.setSectionHeader(null);
				view.setDividerVisible(true);
			} else
			{
				final int section = getSectionForPosition(position);
				if (getPositionForSection(section) == position)
				{
					String title = (String) mIndexer.getSections()[section];
					view.setSectionHeader(title);
				} else
				{
					view.setDividerVisible(false);
					view.setSectionHeader(null);
				}

				// move the divider for the last item in a section
				if (getPositionForSection(section + 1) - 1 == position)
				{
					view.setDividerVisible(false);
				} else
				{
					view.setDividerVisible(true);
				}
			}
		}

		@Override
		public void changeCursor(Cursor cursor)
		{
			if (cursor != null)
			{
				setLoading(false);
			}

			// Get the split between starred and frequent items, if the mode is
			// strequent
			mFrequentSeparatorPos = ListView.INVALID_POSITION;
			int cursorCount = 0;
			if (cursor != null && (cursorCount = cursor.getCount()) > 0
					&& mMode == MODE_STREQUENT)
			{
				cursor.move(-1);
				for (int i = 0; cursor.moveToNext(); i++)
				{
					int starred = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
					if (starred == 0)
					{
						if (i > 0)
						{
							// Only add the separator when there are starred
							// items present
							mFrequentSeparatorPos = i;
						}
						break;
					}
				}
			}

			if (cursor != null && mSearchResultsMode)
			{
				TextView foundContactsText = (TextView) findViewById(R.id.search_results_found);
				String text = getQuantityText(cursor.getCount(),
						R.string.listFoundAllContactsZero,
						R.plurals.listFoundAllContacts);
				foundContactsText.setText(text);
			}

			super.changeCursor(cursor);
			// Update the indexer for the fast scroll widget
			updateIndexer(cursor);
		}

		private void updateIndexer(Cursor cursor)
		{
			if (cursor == null)
			{
				mIndexer = null;
				return;
			}

			Bundle bundle = cursor.getExtras();
			if (bundle.containsKey(EXTRA_ADDRESS_BOOK_INDEX_TITLES))
			{
				String sections[] = bundle
						.getStringArray(EXTRA_ADDRESS_BOOK_INDEX_TITLES);
				int counts[] = bundle
						.getIntArray(EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
				mIndexer = new ContactsSectionIndexer(sections, counts);
			} else
			{
				mIndexer = null;
			}
		}

		/**
		 * Run the query on a helper thread. Beware that this code does not run
		 * on the main UI thread!
		 */
		@Override
		public Cursor runQueryOnBackgroundThread(CharSequence constraint)
		{
			return doFilter(constraint.toString());
		}

		public Object[] getSections()
		{
			if (mIndexer == null)
			{
				return new String[] { " " };
			} else
			{
				return mIndexer.getSections();
			}
		}

		public int getPositionForSection(int sectionIndex)
		{
			if (mIndexer == null)
			{
				return -1;
			}

			return mIndexer.getPositionForSection(sectionIndex);
		}

		public int getSectionForPosition(int position)
		{
			if (mIndexer == null)
			{
				return -1;
			}

			return mIndexer.getSectionForPosition(position);
		}

		@Override
		public boolean areAllItemsEnabled()
		{
			return mMode != MODE_STARRED && !mShowNumberOfContacts
					&& mSuggestionsCursorCount == 0;
		}

		@Override
		public boolean isEnabled(int position)
		{
			if (mShowNumberOfContacts)
			{
				if (position == 0)
				{
					return false;
				}
				position--;
			}

			if (mSuggestionsCursorCount > 0)
			{
				return position != 0 && position != mSuggestionsCursorCount + 1;
			}
			return position != mFrequentSeparatorPos;
		}

		@Override
		public int getCount()
		{
//			if (!mDataValid)
//			{
//				return 0;
//			}
			int superCount = super.getCount();

			if (mShowNumberOfContacts && (mSearchMode || superCount > 0))
			{
				// We don't want to count this header if it's the only thing
				// visible, so that
				// the empty text will display.
				superCount++;
			}

			if (mSearchMode)
			{
				// Last element in the list is the "Find
				superCount++;
			}

			// We do not show the "Create New" button in Search mode
			if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode)
			{
				// Count the "Create new contact" line
				superCount++;
			}

			if (mSuggestionsCursorCount != 0)
			{
				// When showing suggestions, we have 2 additional list items:
				// the "Suggestions"
				// and "All contacts" headers.
				return mSuggestionsCursorCount + superCount + 2;
			} else if (mFrequentSeparatorPos != ListView.INVALID_POSITION)
			{
				// When showing strequent list, we have an additional list item
				// - the separator.
				return superCount + 1;
			} else
			{
				return superCount;
			}
		}

		/**
		 * Gets the actual count of contacts and excludes all the headers.
		 */
		public int getRealCount()
		{
			return super.getCount();
		}

		private int getRealPosition(int pos)
		{
			if (mShowNumberOfContacts)
			{
				pos--;
			}

			if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode)
			{
				return pos - 1;
			} else if (mSuggestionsCursorCount != 0)
			{
				// When showing suggestions, we have 2 additional list items:
				// the "Suggestions"
				// and "All contacts" separators.
				if (pos < mSuggestionsCursorCount + 2)
				{
					// We are in the upper partition (Suggestions). Adjusting
					// for the "Suggestions"
					// separator.
					return pos - 1;
				} else
				{
					// We are in the lower partition (All contacts). Adjusting
					// for the size
					// of the upper partition plus the two separators.
					return pos - mSuggestionsCursorCount - 2;
				}
			} else if (mFrequentSeparatorPos == ListView.INVALID_POSITION)
			{
				// No separator, identity map
				return pos;
			} else if (pos <= mFrequentSeparatorPos)
			{
				// Before or at the separator, identity map
				return pos;
			} else
			{
				// After the separator, remove 1 from the pos to get the real
				// underlying pos
				return pos - 1;
			}
		}

		@Override
		public Object getItem(int pos)
		{
			if (mSuggestionsCursorCount != 0 && pos <= mSuggestionsCursorCount)
			{
				mSuggestionsCursor.moveToPosition(getRealPosition(pos));
				return mSuggestionsCursor;
			} else if (isSearchAllContactsItemPosition(pos))
			{
				return null;
			} else
			{
				int realPosition = getRealPosition(pos);
				if (realPosition < 0)
				{
					return null;
				}
				return super.getItem(realPosition);
			}
		}

		@Override
		public long getItemId(int pos)
		{
//			if (mSuggestionsCursorCount != 0
//					&& pos < mSuggestionsCursorCount + 2)
//			{
//				if (mSuggestionsCursor.moveToPosition(pos - 1))
//				{
//					return mSuggestionsCursor.getLong(mRowIDColumn);
//				} else
//				{
//					return 0;
//				}
//			} else if (isSearchAllContactsItemPosition(pos))
//			{
//				return 0;
//			}
			int realPosition = getRealPosition(pos);
			if (realPosition < 0)
			{
				return 0;
			}
			return super.getItemId(realPosition);
		}

		public void onScroll(AbsListView view, int firstVisibleItem,
				int visibleItemCount, int totalItemCount)
		{
			if (view instanceof PinnedHeaderListView)
			{
				((PinnedHeaderListView) view)
						.configureHeaderView(firstVisibleItem);
			}
		}

		public void onScrollStateChanged(AbsListView view, int scrollState)
		{
			if (mHighlightWhenScrolling)
			{
				if (scrollState != OnScrollListener.SCROLL_STATE_IDLE)
				{
					mHighlightingAnimation.startHighlighting();
				} else
				{
					mHighlightingAnimation.stopHighlighting();
				}
			}

			if (scrollState == OnScrollListener.SCROLL_STATE_FLING)
			{
				mPhotoLoader.pause();
			} else if (mDisplayPhotos)
			{
				mPhotoLoader.resume();
			}
		}

		/**
		 * Computes the state of the pinned header. It can be invisible, fully
		 * visible or partially pushed up out of the view.
		 */
		public int getPinnedHeaderState(int position)
		{
//			if (mIndexer == null || mCursor == null || mCursor.getCount() == 0)
			if (mIndexer == null)
			{
				return PINNED_HEADER_GONE;
			}

			int realPosition = getRealPosition(position);
			if (realPosition < 0)
			{
				return PINNED_HEADER_GONE;
			}

			// The header should get pushed up if the top item shown
			// is the last item in a section for a particular letter.
			int section = getSectionForPosition(realPosition);
			int nextSectionPosition = getPositionForSection(section + 1);
			if (nextSectionPosition != -1
					&& realPosition == nextSectionPosition - 1)
			{
				return PINNED_HEADER_PUSHED_UP;
			}

			return PINNED_HEADER_VISIBLE;
		}

		/**
		 * Configures the pinned header by setting the appropriate text label
		 * and also adjusting color if necessary. The color needs to be
		 * adjusted when the pinned header is being pushed up from the view.
		 */
		public void configurePinnedHeader(View header, int position, int alpha)
		{
			PinnedHeaderCache cache = (PinnedHeaderCache) header.getTag();
			if (cache == null)
			{
				cache = new PinnedHeaderCache();
				cache.titleView = (TextView) header
						.findViewById(R.id.header_text);
				cache.textColor = cache.titleView.getTextColors();
				cache.background = header.getBackground();
				header.setTag(cache);
			}

			int realPosition = getRealPosition(position);
			int section = getSectionForPosition(realPosition);

			String title = (String) mIndexer.getSections()[section];
			cache.titleView.setText(title);

			if (alpha == 255)
			{
				// Opaque: use the default background, and the original text
				// color
				header.setBackgroundDrawable(cache.background);
				cache.titleView.setTextColor(cache.textColor);
			} else
			{
				// Faded: use a solid color approximation of the background, and
				// a translucent text color
				header.setBackgroundColor(Color.rgb(Color
						.red(mPinnedHeaderBackgroundColor)
						* alpha / 255, Color
						.green(mPinnedHeaderBackgroundColor)
						* alpha / 255, Color.blue(mPinnedHeaderBackgroundColor)
						* alpha / 255));

				int textColor = cache.textColor.getDefaultColor();
				cache.titleView.setTextColor(Color.argb(alpha, Color
						.red(textColor), Color.green(textColor), Color
						.blue(textColor)));
			}
		}
	}
}